diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /embedding/components/webbrowserpersist | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'embedding/components/webbrowserpersist')
26 files changed, 6484 insertions, 0 deletions
diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl new file mode 100644 index 000000000..143af0ebd --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl @@ -0,0 +1,90 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PContent; +include protocol PWebBrowserPersistResources; +include protocol PWebBrowserPersistSerialize; + +include InputStreamParams; + +namespace mozilla { + +// nsIWebBrowserPersistDocument has attributes which can be read +// synchronously. To avoid using sync IPC for them, the actor sends +// this structure from the child to the parent before the parent actor +// is exposed to XPCOM. +struct WebBrowserPersistDocumentAttrs { + bool isPrivate; + nsCString documentURI; + nsCString baseURI; + nsCString contentType; + nsCString characterSet; + nsString title; + nsString referrer; + nsString contentDisposition; + uint32_t cacheKey; + uint32_t persistFlags; +}; + +// IPDL doesn't have tuples, so this gives the pair of strings from +// nsIWebBrowserPersistURIMap::getURIMapping a name. +struct WebBrowserPersistURIMapEntry { + nsCString mapFrom; + nsCString mapTo; +}; + +// nsIWebBrowserPersistURIMap is just copied over IPC as one of these, +// not proxied, to simplify the protocol. +struct WebBrowserPersistURIMap { + WebBrowserPersistURIMapEntry[] mapURIs; + nsCString targetBaseURI; +}; + +// This remotes nsIWebBrowserPersistDocument and its visitors. The +// lifecycle is a little complicated: the initial document is +// constructed parent->child, but subdocuments are constructed +// child->parent and then passed back. Subdocuments aren't subactors, +// because that would impose a lifetime relationship that doesn't +// exist in the XPIDL; instead they're all managed by the enclosing +// PContent. +protocol PWebBrowserPersistDocument { + manager PContent; + manages PWebBrowserPersistResources; + manages PWebBrowserPersistSerialize; + +parent: + // The actor isn't exposed to XPCOM until after it gets one of these + // two messages; see also the state transition rules. The message + // is either a response to the constructor (if it was parent->child) + // or sent after it (if it was child->parent). + async Attributes(WebBrowserPersistDocumentAttrs aAttrs, + OptionalInputStreamParams postData, + FileDescriptor[] postFiles); + async InitFailure(nsresult aStatus); + +child: + async SetPersistFlags(uint32_t aNewFlags); + async PWebBrowserPersistResources(); + async PWebBrowserPersistSerialize(WebBrowserPersistURIMap aMap, + nsCString aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn); + async __delete__(); + +state START: + recv Attributes goto MAIN; + recv InitFailure goto FAILED; + +state MAIN: + send SetPersistFlags goto MAIN; + send PWebBrowserPersistResources goto MAIN; + send PWebBrowserPersistSerialize goto MAIN; + send __delete__; + +state FAILED: + send __delete__; +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl new file mode 100644 index 000000000..3c36ae495 --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl @@ -0,0 +1,26 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PWebBrowserPersistDocument; + +namespace mozilla { + +// == nsIWebBrowserPersistResourceVisitor +protocol PWebBrowserPersistResources { + manager PWebBrowserPersistDocument; + +parent: + async VisitResource(nsCString aURI); + + // The actor sent here is in the START state; the parent-side + // receiver will have to wait for it to enter the MAIN state + // before exposing it with a visitDocument call. + async VisitDocument(PWebBrowserPersistDocument aSubDocument); + + // This reflects the endVisit method. + async __delete__(nsresult aStatus); +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl new file mode 100644 index 000000000..ff09fd388 --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl @@ -0,0 +1,29 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PWebBrowserPersistDocument; + +namespace mozilla { + +// This actor represents both an nsIWebBrowserPersistWriteCompletion +// and the nsIOutputStream passed with it to the writeContent method. +protocol PWebBrowserPersistSerialize { + manager PWebBrowserPersistDocument; + +parent: + // This sends the data with no flow control, so the parent could + // wind up buffering an arbitrarily large amount of data... but + // it's a serialized DOM that's already in memory as DOM nodes, so + // this is at worst just a constant-factor increase in memory usage. + // Also, Chromium does the same thing; see + // content::RenderViewImpl::didSerializeDataForFrame. + async WriteData(uint8_t[] aData); + + // This is the onFinish method. + async __delete__(nsCString aContentType, + nsresult aStatus); +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp new file mode 100644 index 000000000..9e152cd46 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistDocumentChild.h" + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIDocument.h" +#include "nsIInputStream.h" +#include "WebBrowserPersistLocalDocument.h" +#include "WebBrowserPersistResourcesChild.h" +#include "WebBrowserPersistSerializeChild.h" + +namespace mozilla { + +WebBrowserPersistDocumentChild::WebBrowserPersistDocumentChild() +{ +} + +WebBrowserPersistDocumentChild::~WebBrowserPersistDocumentChild() +{ +} + +void +WebBrowserPersistDocumentChild::Start(nsIDocument* aDocument) +{ + RefPtr<WebBrowserPersistLocalDocument> doc; + if (aDocument) { + doc = new WebBrowserPersistLocalDocument(aDocument); + } + Start(doc); +} + +void +WebBrowserPersistDocumentChild::Start(nsIWebBrowserPersistDocument* aDocument) +{ + MOZ_ASSERT(!mDocument); + if (!aDocument) { + SendInitFailure(NS_ERROR_FAILURE); + return; + } + + WebBrowserPersistDocumentAttrs attrs; + nsCOMPtr<nsIInputStream> postDataStream; + OptionalInputStreamParams postData; + nsTArray<FileDescriptor> postFiles; +#define ENSURE(e) do { \ + nsresult rv = (e); \ + if (NS_FAILED(rv)) { \ + SendInitFailure(rv); \ + return; \ + } \ + } while(0) + ENSURE(aDocument->GetIsPrivate(&(attrs.isPrivate()))); + ENSURE(aDocument->GetDocumentURI(attrs.documentURI())); + ENSURE(aDocument->GetBaseURI(attrs.baseURI())); + ENSURE(aDocument->GetContentType(attrs.contentType())); + ENSURE(aDocument->GetCharacterSet(attrs.characterSet())); + ENSURE(aDocument->GetTitle(attrs.title())); + ENSURE(aDocument->GetReferrer(attrs.referrer())); + ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition())); + ENSURE(aDocument->GetCacheKey(&(attrs.cacheKey()))); + ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags()))); + ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream))); + ipc::SerializeInputStream(postDataStream, + postData, + postFiles); +#undef ENSURE + mDocument = aDocument; + SendAttributes(attrs, postData, postFiles); +} + +bool +WebBrowserPersistDocumentChild::RecvSetPersistFlags(const uint32_t& aNewFlags) +{ + mDocument->SetPersistFlags(aNewFlags); + return true; +} + +PWebBrowserPersistResourcesChild* +WebBrowserPersistDocumentChild::AllocPWebBrowserPersistResourcesChild() +{ + auto* actor = new WebBrowserPersistResourcesChild(); + NS_ADDREF(actor); + return actor; +} + +bool +WebBrowserPersistDocumentChild::RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) +{ + RefPtr<WebBrowserPersistResourcesChild> visitor = + static_cast<WebBrowserPersistResourcesChild*>(aActor); + nsresult rv = mDocument->ReadResources(visitor); + if (NS_FAILED(rv)) { + // This is a sync failure on the child side but an async + // failure on the parent side -- it already got NS_OK from + // ReadResources, so the error has to be reported via the + // visitor instead. + visitor->EndVisit(mDocument, rv); + } + return true; +} + +bool +WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) +{ + auto* castActor = + static_cast<WebBrowserPersistResourcesChild*>(aActor); + NS_RELEASE(castActor); + return true; +} + +PWebBrowserPersistSerializeChild* +WebBrowserPersistDocumentChild::AllocPWebBrowserPersistSerializeChild( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + auto* actor = new WebBrowserPersistSerializeChild(aMap); + NS_ADDREF(actor); + return actor; +} + +bool +WebBrowserPersistDocumentChild::RecvPWebBrowserPersistSerializeConstructor( + PWebBrowserPersistSerializeChild* aActor, + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + auto* castActor = + static_cast<WebBrowserPersistSerializeChild*>(aActor); + // This actor performs the roles of: completion, URI map, and output stream. + nsresult rv = mDocument->WriteContent(castActor, + castActor, + aRequestedContentType, + aEncoderFlags, + aWrapColumn, + castActor); + if (NS_FAILED(rv)) { + castActor->OnFinish(mDocument, castActor, aRequestedContentType, rv); + } + return true; +} + +bool +WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) +{ + auto* castActor = + static_cast<WebBrowserPersistSerializeChild*>(aActor); + NS_RELEASE(castActor); + return true; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h new file mode 100644 index 000000000..a72291f3c --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistDocumentChild_h__ +#define WebBrowserPersistDocumentChild_h__ + +#include "mozilla/PWebBrowserPersistDocumentChild.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +class nsIDocument; + +namespace mozilla { + +class WebBrowserPersistDocumentChild final + : public PWebBrowserPersistDocumentChild +{ +public: + WebBrowserPersistDocumentChild(); + ~WebBrowserPersistDocumentChild(); + + // This sends either Attributes or InitFailure and thereby causes + // the actor to leave the START state. + void Start(nsIWebBrowserPersistDocument* aDocument); + void Start(nsIDocument* aDocument); + + virtual bool + RecvSetPersistFlags(const uint32_t& aNewFlags) override; + + virtual PWebBrowserPersistResourcesChild* + AllocPWebBrowserPersistResourcesChild() override; + virtual bool + RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) override; + virtual bool + DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) override; + + virtual PWebBrowserPersistSerializeChild* + AllocPWebBrowserPersistSerializeChild( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + RecvPWebBrowserPersistSerializeConstructor( + PWebBrowserPersistSerializeChild* aActor, + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) override; + +private: + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp new file mode 100644 index 000000000..248b05b9b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistDocumentParent.h" + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIInputStream.h" +#include "nsThreadUtils.h" +#include "WebBrowserPersistResourcesParent.h" +#include "WebBrowserPersistSerializeParent.h" +#include "WebBrowserPersistRemoteDocument.h" + +namespace mozilla { + +WebBrowserPersistDocumentParent::WebBrowserPersistDocumentParent() +: mReflection(nullptr) +{ +} + +void +WebBrowserPersistDocumentParent::SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady) +{ + MOZ_ASSERT(aOnReady); + MOZ_ASSERT(!mOnReady); + MOZ_ASSERT(!mReflection); + mOnReady = aOnReady; +} + +void +WebBrowserPersistDocumentParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mReflection) { + mReflection->ActorDestroy(); + mReflection = nullptr; + } + if (mOnReady) { + // Bug 1202887: If this is part of a subtree destruction, then + // anything which could cause another actor in that subtree to + // be Send__delete__()ed will cause use-after-free -- such as + // dropping the last reference to another document's + // WebBrowserPersistRemoteDocument. To avoid that, defer the + // callback until after the entire subtree is destroyed. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsresult>(mOnReady, &nsIWebBrowserPersistDocumentReceiver::OnError, + NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + mOnReady = nullptr; + } +} + +WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent() +{ + MOZ_RELEASE_ASSERT(!mReflection); + MOZ_ASSERT(!mOnReady); +} + +bool +WebBrowserPersistDocumentParent::RecvAttributes(const Attrs& aAttrs, + const OptionalInputStreamParams& aPostData, + nsTArray<FileDescriptor>&& aPostFiles) +{ + // Deserialize the postData unconditionally so that fds aren't leaked. + nsCOMPtr<nsIInputStream> postData = + ipc::DeserializeInputStream(aPostData, aPostFiles); + if (!mOnReady || mReflection) { + return false; + } + mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData); + RefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection; + mOnReady->OnDocumentReady(reflection); + mOnReady = nullptr; + return true; +} + +bool +WebBrowserPersistDocumentParent::RecvInitFailure(const nsresult& aFailure) +{ + if (!mOnReady || mReflection) { + return false; + } + mOnReady->OnError(aFailure); + mOnReady = nullptr; + // Warning: Send__delete__ deallocates this object. + return Send__delete__(this); +} + +PWebBrowserPersistResourcesParent* +WebBrowserPersistDocumentParent::AllocPWebBrowserPersistResourcesParent() +{ + MOZ_CRASH("Don't use this; construct the actor directly and AddRef."); + return nullptr; +} + +bool +WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) +{ + // Turn the ref held by IPC back into an nsRefPtr. + RefPtr<WebBrowserPersistResourcesParent> actor = + already_AddRefed<WebBrowserPersistResourcesParent>( + static_cast<WebBrowserPersistResourcesParent*>(aActor)); + return true; +} + +PWebBrowserPersistSerializeParent* +WebBrowserPersistDocumentParent::AllocPWebBrowserPersistSerializeParent( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + MOZ_CRASH("Don't use this; construct the actor directly."); + return nullptr; +} + +bool +WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) +{ + delete aActor; + return true; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h new file mode 100644 index 000000000..b02b37706 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistDocumentParent_h__ +#define WebBrowserPersistDocumentParent_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PWebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +// This class is the IPC half of the glue between the +// nsIWebBrowserPersistDocument interface and a remote document. When +// (and if) it receives the Attributes message it constructs an +// WebBrowserPersistRemoteDocument and releases it into the XPCOM +// universe; otherwise, it invokes the document receiver's error +// callback. +// +// This object's lifetime is the normal IPC lifetime; on destruction, +// it calls its XPCOM reflection (if it exists yet) to remove that +// reference. Normal deletion occurs when the XPCOM object is being +// destroyed or after an InitFailure is received and handled. +// +// See also: TabParent::StartPersistence. + +namespace mozilla { + +class WebBrowserPersistRemoteDocument; + +class WebBrowserPersistDocumentParent final + : public PWebBrowserPersistDocumentParent +{ +public: + WebBrowserPersistDocumentParent(); + virtual ~WebBrowserPersistDocumentParent(); + + // Set a callback to be invoked when the actor leaves the START + // state. This method must be called exactly once while the actor + // is still in the START state (or is unconstructed). + void SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady); + + using Attrs = WebBrowserPersistDocumentAttrs; + + // IPDL methods: + virtual bool + RecvAttributes(const Attrs& aAttrs, + const OptionalInputStreamParams& aPostData, + nsTArray<FileDescriptor>&& aPostFiles) override; + virtual bool + RecvInitFailure(const nsresult& aFailure) override; + + virtual PWebBrowserPersistResourcesParent* + AllocPWebBrowserPersistResourcesParent() override; + virtual bool + DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) override; + + virtual PWebBrowserPersistSerializeParent* + AllocPWebBrowserPersistSerializeParent( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; +private: + // This is reset to nullptr when the callback is invoked. + nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> mOnReady; + WebBrowserPersistRemoteDocument* mReflection; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentParent_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp new file mode 100644 index 000000000..23513387e --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp @@ -0,0 +1,1475 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "WebBrowserPersistLocalDocument.h" +#include "WebBrowserPersistDocumentParent.h" + +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSharedElement.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" +#include "mozilla/dom/TabParent.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsContentCID.h" +#include "nsCycleCollectionParticipant.h" +#include "nsFrameLoader.h" +#include "nsIComponentRegistrar.h" +#include "nsIContent.h" +#include "nsIDOMAttr.h" +#include "nsIDOMComment.h" +#include "nsIDOMDocument.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIDOMHTMLAppletElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLBaseElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLEmbedElement.h" +#include "nsIDOMHTMLFrameElement.h" +#include "nsIDOMHTMLIFrameElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLMediaElement.h" +#include "nsIDOMHTMLObjectElement.h" +#include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMHTMLScriptElement.h" +#include "nsIDOMHTMLSourceElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIDOMMozNamedAttrMap.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeFilter.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMProcessingInstruction.h" +#include "nsIDOMTreeWalker.h" +#include "nsIDOMWindowUtils.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDocumentEncoder.h" +#include "nsILoadContext.h" +#include "nsIProtocolHandler.h" +#include "nsISHEntry.h" +#include "nsISupportsPrimitives.h" +#include "nsITabParent.h" +#include "nsIWebBrowserPersist.h" +#include "nsIWebNavigation.h" +#include "nsIWebPageDescriptor.h" +#include "nsNetUtil.h" + +namespace mozilla { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument) + + +WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(nsIDocument* aDocument) +: mDocument(aDocument) +, mPersistFlags(0) +{ + MOZ_ASSERT(mDocument); +} + +WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() +{ +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) +{ + mPersistFlags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) +{ + *aFlags = mPersistFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) +{ + nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext(); + *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) +{ + nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI(); + if (!uri) { + return NS_ERROR_UNEXPECTED; + } + return uri->GetSpec(aURISpec); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) +{ + nsCOMPtr<nsIURI> uri = GetBaseURI(); + if (!uri) { + return NS_ERROR_UNEXPECTED; + } + return uri->GetSpec(aURISpec); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) +{ + nsAutoString utf16Type; + nsresult rv; + + rv = mDocument->GetContentType(utf16Type); + NS_ENSURE_SUCCESS(rv, rv); + aContentType = NS_ConvertUTF16toUTF8(utf16Type); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) +{ + aCharSet = GetCharacterSet(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) +{ + nsAutoString titleBuffer; + mDocument->GetTitle(titleBuffer); + aTitle = titleBuffer; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetReferrer(nsAString& aReferrer) +{ + mDocument->GetReferrer(aReferrer); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) +{ + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView(); + if (NS_WARN_IF(!window)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window); + if (NS_WARN_IF(!utils)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsresult rv = utils->GetDocumentMetadata( + NS_LITERAL_STRING("content-disposition"), aCD); + if (NS_WARN_IF(NS_FAILED(rv))) { + aCD.SetIsVoid(true); + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) +{ + nsCOMPtr<nsISHEntry> history = GetHistory(); + if (!history) { + *aKey = 0; + return NS_OK; + } + nsCOMPtr<nsISupports> abstractKey; + nsresult rv = history->GetCacheKey(getter_AddRefs(abstractKey)); + if (NS_WARN_IF(NS_FAILED(rv)) || !abstractKey) { + *aKey = 0; + return NS_OK; + } + nsCOMPtr<nsISupportsPRUint32> u32 = do_QueryInterface(abstractKey); + if (NS_WARN_IF(!u32)) { + *aKey = 0; + return NS_OK; + } + return u32->GetData(aKey); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) +{ + nsCOMPtr<nsISHEntry> history = GetHistory(); + if (!history) { + *aStream = nullptr; + return NS_OK; + } + return history->GetPostData(aStream); +} + +already_AddRefed<nsISHEntry> +WebBrowserPersistLocalDocument::GetHistory() +{ + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView(); + if (NS_WARN_IF(!window)) { + return nullptr; + } + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window); + if (NS_WARN_IF(!webNav)) { + return nullptr; + } + nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav); + if (NS_WARN_IF(!desc)) { + return nullptr; + } + nsCOMPtr<nsISupports> curDesc; + nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc)); + // This can fail if, e.g., the document is a Print Preview. + if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) { + return nullptr; + } + nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc); + return history.forget(); +} + +const nsCString& +WebBrowserPersistLocalDocument::GetCharacterSet() const +{ + return mDocument->GetDocumentCharacterSet(); +} + +uint32_t +WebBrowserPersistLocalDocument::GetPersistFlags() const +{ + return mPersistFlags; +} + + +already_AddRefed<nsIURI> +WebBrowserPersistLocalDocument::GetBaseURI() const +{ + return mDocument->GetBaseURI(); +} + + +namespace { + +// Helper class for ReadResources(). +class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver { +public: + ResourceReader(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistResourceVisitor* aVisitor); + nsresult OnWalkDOMNode(nsIDOMNode* aNode); + + // This is called both to indicate the end of the document walk + // and when a subdocument is (maybe asynchronously) sent to the + // visitor. The call to EndVisit needs to happen after both of + // those have finished. + void DocumentDone(nsresult aStatus); + + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER + NS_DECL_ISUPPORTS + +private: + RefPtr<WebBrowserPersistLocalDocument> mParent; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor; + nsCOMPtr<nsIURI> mCurrentBaseURI; + uint32_t mPersistFlags; + + // The number of DocumentDone calls after which EndVisit will be + // called on the visitor. Counts the main document if it's still + // being walked and any outstanding asynchronous subdocument + // StartPersistence calls. + size_t mOutstandingDocuments; + // Collects the status parameters to DocumentDone calls. + nsresult mEndStatus; + + nsresult OnWalkURI(const nsACString& aURISpec); + nsresult OnWalkURI(nsIURI* aURI); + nsresult OnWalkAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI = ""); + nsresult OnWalkSubframe(nsIDOMNode* aNode); + + bool IsFlagSet(uint32_t aFlag) const { + return mParent->GetPersistFlags() & aFlag; + } + + ~ResourceReader(); + + using IWBP = nsIWebBrowserPersist; +}; + +NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver) + +ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistResourceVisitor* aVisitor) +: mParent(aParent) +, mVisitor(aVisitor) +, mCurrentBaseURI(aParent->GetBaseURI()) +, mPersistFlags(aParent->GetPersistFlags()) +, mOutstandingDocuments(1) +, mEndStatus(NS_OK) +{ + MOZ_ASSERT(mCurrentBaseURI); +} + +ResourceReader::~ResourceReader() +{ + MOZ_ASSERT(mOutstandingDocuments == 0); +} + +void +ResourceReader::DocumentDone(nsresult aStatus) +{ + MOZ_ASSERT(mOutstandingDocuments > 0); + if (NS_SUCCEEDED(mEndStatus)) { + mEndStatus = aStatus; + } + if (--mOutstandingDocuments == 0) { + mVisitor->EndVisit(mParent, mEndStatus); + } +} + +nsresult +ResourceReader::OnWalkSubframe(nsIDOMNode* aNode) +{ + nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode); + NS_ENSURE_STATE(loaderOwner); + RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader(); + NS_ENSURE_STATE(loader); + + ++mOutstandingDocuments; + // Pass in 0 as the outer window ID so that we start + // persisting the root of this subframe, and not some other + // subframe child of this subframe. + nsresult rv = loader->StartPersistence(0, this); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_NO_CONTENT) { + // Just ignore frames with no content document. + rv = NS_OK; + } + // StartPersistence won't eventually call this if it failed, + // so this does so (to keep mOutstandingDocuments correct). + DocumentDone(rv); + } + return rv; +} + +NS_IMETHODIMP +ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) +{ + mVisitor->VisitDocument(mParent, aDocument); + DocumentDone(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +ResourceReader::OnError(nsresult aFailure) +{ + DocumentDone(aFailure); + return NS_OK; +} + +nsresult +ResourceReader::OnWalkURI(nsIURI* aURI) +{ + // Test if this URI should be persisted. By default + // we should assume the URI is persistable. + bool doNotPersistURI; + nsresult rv = NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_NON_PERSISTABLE, + &doNotPersistURI); + if (NS_SUCCEEDED(rv) && doNotPersistURI) { + return NS_OK; + } + + nsAutoCString stringURI; + rv = aURI->GetSpec(stringURI); + NS_ENSURE_SUCCESS(rv, rv); + return mVisitor->VisitResource(mParent, stringURI); +} + +nsresult +ResourceReader::OnWalkURI(const nsACString& aURISpec) +{ + nsresult rv; + nsCOMPtr<nsIURI> uri; + + rv = NS_NewURI(getter_AddRefs(uri), + aURISpec, + mParent->GetCharacterSet().get(), + mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + return OnWalkURI(uri); +} + +static nsresult +ExtractAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI, + nsCString& aValue) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + // Find the named URI attribute on the (element) node and store + // a reference to the URI that maps onto a local file name + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); + NS_ConvertASCIItoUTF16 attribute(aAttribute); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); + NS_ENSURE_SUCCESS(rv, rv); + if (attr) { + nsAutoString value; + rv = attr->GetValue(value); + NS_ENSURE_SUCCESS(rv, rv); + aValue = NS_ConvertUTF16toUTF8(value); + } else { + aValue.Truncate(); + } + return NS_OK; +} + +nsresult +ResourceReader::OnWalkAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI) +{ + nsAutoCString uriSpec; + nsresult rv = ExtractAttribute(aNode, aAttribute, aNamespaceURI, uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (uriSpec.IsEmpty()) { + return NS_OK; + } + return OnWalkURI(uriSpec); +} + +static nsresult +GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref) +{ + nsresult rv; + nsAutoString data; + rv = aPI->GetData(data); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref); + return NS_OK; +} + +nsresult +ResourceReader::OnWalkDOMNode(nsIDOMNode* aNode) +{ + nsresult rv; + + // Fixup xml-stylesheet processing instructions + nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode); + if (nodeAsPI) { + nsAutoString target; + rv = nodeAsPI->GetTarget(target); + NS_ENSURE_SUCCESS(rv, rv); + if (target.EqualsLiteral("xml-stylesheet")) { + nsAutoString href; + GetXMLStyleSheetLink(nodeAsPI, href); + if (!href.IsEmpty()) { + return OnWalkURI(NS_ConvertUTF16toUTF8(href)); + } + } + return NS_OK; + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + if (!content) { + return NS_OK; + } + + // Test the node to see if it's an image, frame, iframe, css, js + nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode); + if (nodeAsImage) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsSVGElement(nsGkAtoms::img)) { + return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink"); + } + + nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNode); + if (nodeAsMedia) { + return OnWalkAttribute(aNode, "src"); + } + nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNode); + if (nodeAsSource) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsHTMLElement(nsGkAtoms::body)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsHTMLElement(nsGkAtoms::table)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsHTMLElement(nsGkAtoms::tr)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) { + return OnWalkAttribute(aNode, "background"); + } + + nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode); + if (nodeAsScript) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsSVGElement(nsGkAtoms::script)) { + return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink"); + } + + nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode); + if (nodeAsEmbed) { + return OnWalkAttribute(aNode, "src"); + } + + nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode); + if (nodeAsObject) { + return OnWalkAttribute(aNode, "data"); + } + + nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode); + if (nodeAsApplet) { + // For an applet, relative URIs are resolved relative to the + // codebase (which is resolved relative to the base URI). + nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI; + nsAutoString codebase; + rv = nodeAsApplet->GetCodeBase(codebase); + NS_ENSURE_SUCCESS(rv, rv); + if (!codebase.IsEmpty()) { + nsCOMPtr<nsIURI> baseURI; + rv = NS_NewURI(getter_AddRefs(baseURI), codebase, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + if (baseURI) { + mCurrentBaseURI = baseURI; + // Must restore this before returning (or ENSURE'ing). + } + } + + // We only store 'code' locally if there is no 'archive', + // otherwise we assume the archive file(s) contains it (bug 430283). + nsAutoCString archiveAttr; + rv = ExtractAttribute(aNode, "archive", "", archiveAttr); + if (NS_SUCCEEDED(rv)) { + if (!archiveAttr.IsEmpty()) { + rv = OnWalkURI(archiveAttr); + } else { + rv = OnWalkAttribute(aNode, "core"); + } + } + + // restore the base URI we really want to have + mCurrentBaseURI = oldBase; + return rv; + } + + nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode); + if (nodeAsLink) { + // Test if the link has a rel value indicating it to be a stylesheet + nsAutoString linkRel; + if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty()) { + nsReadingIterator<char16_t> start; + nsReadingIterator<char16_t> end; + nsReadingIterator<char16_t> current; + + linkRel.BeginReading(start); + linkRel.EndReading(end); + + // Walk through space delimited string looking for "stylesheet" + for (current = start; current != end; ++current) { + // Ignore whitespace + if (nsCRT::IsAsciiSpace(*current)) { + continue; + } + + // Grab the next space delimited word + nsReadingIterator<char16_t> startWord = current; + do { + ++current; + } while (current != end && !nsCRT::IsAsciiSpace(*current)); + + // Store the link for fix up if it says "stylesheet" + if (Substring(startWord, current) + .LowerCaseEqualsLiteral("stylesheet")) { + OnWalkAttribute(aNode, "href"); + return NS_OK; + } + if (current == end) { + break; + } + } + } + return NS_OK; + } + + nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode); + if (nodeAsFrame) { + return OnWalkSubframe(aNode); + } + + nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode); + if (nodeAsIFrame && !(mPersistFlags & + IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) { + return OnWalkSubframe(aNode); + } + + nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode); + if (nodeAsInput) { + return OnWalkAttribute(aNode, "src"); + } + + return NS_OK; +} + +// Helper class for node rewriting in writeContent(). +class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup { +public: + PersistNodeFixup(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistURIMap* aMap, + nsIURI* aTargetURI); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOCUMENTENCODERNODEFIXUP +private: + virtual ~PersistNodeFixup() { } + RefPtr<WebBrowserPersistLocalDocument> mParent; + nsClassHashtable<nsCStringHashKey, nsCString> mMap; + nsCOMPtr<nsIURI> mCurrentBaseURI; + nsCOMPtr<nsIURI> mTargetBaseURI; + + bool IsFlagSet(uint32_t aFlag) const { + return mParent->GetPersistFlags() & aFlag; + } + + nsresult GetNodeToFixup(nsIDOMNode* aNodeIn, nsIDOMNode** aNodeOut); + nsresult FixupURI(nsAString& aURI); + nsresult FixupAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI = ""); + nsresult FixupAnchor(nsIDOMNode* aNode); + nsresult FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI, + const nsAString& aHref); + + using IWBP = nsIWebBrowserPersist; +}; + +NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup) + +PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistURIMap* aMap, + nsIURI* aTargetURI) +: mParent(aParent) +, mCurrentBaseURI(aParent->GetBaseURI()) +, mTargetBaseURI(aTargetURI) +{ + if (aMap) { + uint32_t mapSize; + nsresult rv = aMap->GetNumMappedURIs(&mapSize); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + NS_ENSURE_SUCCESS_VOID(rv); + for (uint32_t i = 0; i < mapSize; ++i) { + nsAutoCString urlFrom; + nsCString* urlTo = new nsCString(); + + rv = aMap->GetURIMapping(i, urlFrom, *urlTo); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv)) { + mMap.Put(urlFrom, urlTo); + } + } + } +} + +nsresult +PersistNodeFixup::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut) +{ + // Avoid mixups in FixupNode that could leak objects; this goes + // against the usual out parameter convention, but it's a private + // method so shouldn't be a problem. + MOZ_ASSERT(!*aNodeOut); + + if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) { + nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut); + NS_ENSURE_SUCCESS(rv, rv); + } else { + NS_ADDREF(*aNodeOut = aNodeIn); + } + nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut)); + if (element) { + // Make sure this is not XHTML + nsAutoString namespaceURI; + element->GetNamespaceURI(namespaceURI); + if (namespaceURI.IsEmpty()) { + // This is a tag-soup node. It may have a _base_href attribute + // stuck on it by the parser, but since we're fixing up all URIs + // relative to the overall document base that will screw us up. + // Just remove the _base_href. + element->RemoveAttribute(NS_LITERAL_STRING("_base_href")); + } + } + return NS_OK; +} + +nsresult +PersistNodeFixup::FixupURI(nsAString &aURI) +{ + // get the current location of the file (absolutized) + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString spec; + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + const nsCString* replacement = mMap.Get(spec); + if (!replacement) { + // Note that most callers ignore this "failure". + return NS_ERROR_FAILURE; + } + if (!replacement->IsEmpty()) { + aURI = NS_ConvertUTF8toUTF16(*replacement); + } + return NS_OK; +} + +nsresult +PersistNodeFixup::FixupAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + NS_ConvertASCIItoUTF16 attribute(aAttribute); + NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); + if (attr) { + nsString uri; + attr->GetValue(uri); + rv = FixupURI(uri); + if (NS_SUCCEEDED(rv)) { + attr->SetValue(uri); + } + } + + return rv; +} + +nsresult +PersistNodeFixup::FixupAnchor(nsIDOMNode *aNode) +{ + if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) { + return NS_OK; + } + + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Make all anchor links absolute so they point off onto the Internet + nsString attribute(NS_LITERAL_STRING("href")); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr)); + if (attr) { + nsString oldValue; + attr->GetValue(oldValue); + NS_ConvertUTF16toUTF8 oldCValue(oldValue); + + // Skip empty values and self-referencing bookmarks + if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') { + return NS_OK; + } + + // if saving file to same location, we don't need to do any fixup + bool isEqual; + if (mTargetBaseURI && + NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) && + isEqual) { + return NS_OK; + } + + nsCOMPtr<nsIURI> relativeURI; + relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) + ? mTargetBaseURI : mCurrentBaseURI; + // Make a new URI to replace the current one + nsCOMPtr<nsIURI> newURI; + rv = NS_NewURI(getter_AddRefs(newURI), oldCValue, + mParent->GetCharacterSet().get(), relativeURI); + if (NS_SUCCEEDED(rv) && newURI) { + newURI->SetUserPass(EmptyCString()); + nsAutoCString uriSpec; + rv = newURI->GetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec)); + } + } + + return NS_OK; +} + +static void +AppendXMLAttr(const nsAString& key, const nsAString& aValue, nsAString& aBuffer) +{ + if (!aBuffer.IsEmpty()) { + aBuffer.Append(' '); + } + aBuffer.Append(key); + aBuffer.AppendLiteral("=\""); + for (size_t i = 0; i < aValue.Length(); ++i) { + switch (aValue[i]) { + case '&': + aBuffer.AppendLiteral("&"); + break; + case '<': + aBuffer.AppendLiteral("<"); + break; + case '>': + aBuffer.AppendLiteral(">"); + break; + case '"': + aBuffer.AppendLiteral("""); + break; + default: + aBuffer.Append(aValue[i]); + break; + } + } + aBuffer.Append('"'); +} + +nsresult +PersistNodeFixup::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI, + const nsAString& aHref) +{ + NS_ENSURE_ARG_POINTER(aPI); + nsresult rv = NS_OK; + + nsAutoString data; + rv = aPI->GetData(data); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsAutoString href; + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::href, + href); + + // Construct and set a new data value for the xml-stylesheet + if (!aHref.IsEmpty() && !href.IsEmpty()) + { + nsAutoString alternate; + nsAutoString charset; + nsAutoString title; + nsAutoString type; + nsAutoString media; + + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::alternate, + alternate); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::charset, + charset); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::title, + title); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::type, + type); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::media, + media); + + nsAutoString newData; + AppendXMLAttr(NS_LITERAL_STRING("href"), aHref, newData); + if (!title.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("title"), title, newData); + } + if (!media.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("media"), media, newData); + } + if (!type.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("type"), type, newData); + } + if (!charset.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("charset"), charset, newData); + } + if (!alternate.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("alternate"), alternate, newData); + } + aPI->SetData(newData); + } + + return rv; +} + +NS_IMETHODIMP +PersistNodeFixup::FixupNode(nsIDOMNode *aNodeIn, + bool *aSerializeCloneKids, + nsIDOMNode **aNodeOut) +{ + *aNodeOut = nullptr; + *aSerializeCloneKids = false; + + uint16_t type; + nsresult rv = aNodeIn->GetNodeType(&type); + NS_ENSURE_SUCCESS(rv, rv); + if (type != nsIDOMNode::ELEMENT_NODE && + type != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + return NS_OK; + } + + // Fixup xml-stylesheet processing instructions + nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn); + if (nodeAsPI) { + nsAutoString target; + nodeAsPI->GetTarget(target); + if (target.EqualsLiteral("xml-stylesheet")) + { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMProcessingInstruction> outNode = + do_QueryInterface(*aNodeOut); + nsAutoString href; + GetXMLStyleSheetLink(nodeAsPI, href); + if (!href.IsEmpty()) { + FixupURI(href); + FixupXMLStyleSheetLink(outNode, href); + } + } + } + return NS_OK; + } + + // BASE elements are replaced by a comment so relative links are not hosed. + if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS)) { + nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn); + if (nodeAsBase) { + nsCOMPtr<nsIDOMDocument> ownerDocument; + auto* base = static_cast<dom::HTMLSharedElement*>(nodeAsBase.get()); + base->GetOwnerDocument(getter_AddRefs(ownerDocument)); + if (ownerDocument) { + nsAutoString href; + base->GetHref(href); // Doesn't matter if this fails + nsCOMPtr<nsIDOMComment> comment; + nsAutoString commentText; + commentText.AssignLiteral(" base "); + if (!href.IsEmpty()) { + commentText += NS_LITERAL_STRING("href=\"") + href + + NS_LITERAL_STRING("\" "); + } + rv = ownerDocument->CreateComment(commentText, + getter_AddRefs(comment)); + if (comment) { + return CallQueryInterface(comment, aNodeOut); + } + } + return NS_OK; + } + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn); + if (!content) { + return NS_OK; + } + + // Fix up href and file links in the elements + nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn); + if (nodeAsAnchor) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAnchor(*aNodeOut); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn); + if (nodeAsArea) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAnchor(*aNodeOut); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::body)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::table)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::tr)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn); + if (nodeAsImage) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) { + imgCon->SetLoadingEnabled(false); + } + FixupAnchor(*aNodeOut); + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNodeIn); + if (nodeAsMedia) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNodeIn); + if (nodeAsSource) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + if (content->IsSVGElement(nsGkAtoms::img)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) + imgCon->SetLoadingEnabled(false); + + // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed? + FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn); + if (nodeAsScript) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + if (content->IsSVGElement(nsGkAtoms::script)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn); + if (nodeAsEmbed) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn); + if (nodeAsObject) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "data"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn); + if (nodeAsApplet) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMHTMLAppletElement> newApplet = + do_QueryInterface(*aNodeOut); + // For an applet, relative URIs are resolved relative to the + // codebase (which is resolved relative to the base URI). + nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI; + nsAutoString codebase; + nodeAsApplet->GetCodeBase(codebase); + if (!codebase.IsEmpty()) { + nsCOMPtr<nsIURI> baseURI; + NS_NewURI(getter_AddRefs(baseURI), codebase, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + if (baseURI) { + mCurrentBaseURI = baseURI; + } + } + // Unset the codebase too, since we'll correctly relativize the + // code and archive paths. + static_cast<dom::HTMLSharedObjectElement*>(newApplet.get())-> + RemoveAttribute(NS_LITERAL_STRING("codebase")); + FixupAttribute(*aNodeOut, "code"); + FixupAttribute(*aNodeOut, "archive"); + // restore the base URI we really want to have + mCurrentBaseURI = oldBase; + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn); + if (nodeAsLink) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // First see if the link represents linked content + rv = FixupAttribute(*aNodeOut, "href"); + if (NS_FAILED(rv)) { + // Perhaps this link is actually an anchor to related content + FixupAnchor(*aNodeOut); + } + // TODO if "type" attribute == "text/css" + // fixup stylesheet + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn); + if (nodeAsFrame) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn); + if (nodeAsIFrame) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn); + if (nodeAsInput) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) { + imgCon->SetLoadingEnabled(false); + } + + FixupAttribute(*aNodeOut, "src"); + + nsAutoString valueStr; + NS_NAMED_LITERAL_STRING(valueAttr, "value"); + // Update element node attributes with user-entered form state + nsCOMPtr<nsIContent> content = do_QueryInterface(*aNodeOut); + RefPtr<dom::HTMLInputElement> outElt = + dom::HTMLInputElement::FromContentOrNull(content); + nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut); + switch (formControl->GetType()) { + case NS_FORM_INPUT_EMAIL: + case NS_FORM_INPUT_SEARCH: + case NS_FORM_INPUT_TEXT: + case NS_FORM_INPUT_TEL: + case NS_FORM_INPUT_URL: + case NS_FORM_INPUT_NUMBER: + case NS_FORM_INPUT_RANGE: + case NS_FORM_INPUT_DATE: + case NS_FORM_INPUT_TIME: + case NS_FORM_INPUT_COLOR: + nodeAsInput->GetValue(valueStr); + // Avoid superfluous value="" serialization + if (valueStr.IsEmpty()) + outElt->RemoveAttribute(valueAttr); + else + outElt->SetAttribute(valueAttr, valueStr); + break; + case NS_FORM_INPUT_CHECKBOX: + case NS_FORM_INPUT_RADIO: + bool checked; + nodeAsInput->GetChecked(&checked); + outElt->SetDefaultChecked(checked); + break; + default: + break; + } + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLTextAreaElement> nodeAsTextArea = do_QueryInterface(aNodeIn); + if (nodeAsTextArea) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Tell the document encoder to serialize the text child we create below + *aSerializeCloneKids = true; + + nsAutoString valueStr; + nodeAsTextArea->GetValue(valueStr); + + (*aNodeOut)->SetTextContent(valueStr); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn); + if (nodeAsOption) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut); + bool selected; + nodeAsOption->GetSelected(&selected); + outElt->SetDefaultSelected(selected); + } + return rv; + } + + return NS_OK; +} + +} // unnamed namespace + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor; + + nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(mDocument); + NS_ENSURE_TRUE(docAsNode, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMTreeWalker> walker; + nsCOMPtr<nsIDOMDocument> oldStyleDoc = do_QueryInterface(mDocument); + MOZ_ASSERT(oldStyleDoc); + rv = oldStyleDoc->CreateTreeWalker(docAsNode, + nsIDOMNodeFilter::SHOW_ELEMENT | + nsIDOMNodeFilter::SHOW_DOCUMENT | + nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION, + nullptr, 1, getter_AddRefs(walker)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + MOZ_ASSERT(walker); + + RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor); + nsCOMPtr<nsIDOMNode> currentNode; + walker->GetCurrentNode(getter_AddRefs(currentNode)); + while (currentNode) { + rv = reader->OnWalkDOMNode(currentNode); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = walker->NextNode(getter_AddRefs(currentNode)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + } + reader->DocumentDone(rv); + // If NS_FAILED(rv), it was / will be reported by an EndVisit call + // via DocumentDone. This method must return a failure if and + // only if visitor won't be invoked. + return NS_OK; +} + +static uint32_t +ConvertEncoderFlags(uint32_t aEncoderFlags) +{ + uint32_t encoderFlags = 0; + + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY) + encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED) + encoderFlags |= nsIDocumentEncoder::OutputFormatted; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW) + encoderFlags |= nsIDocumentEncoder::OutputRaw; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY) + encoderFlags |= nsIDocumentEncoder::OutputBodyOnly; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED) + encoderFlags |= nsIDocumentEncoder::OutputPreformatted; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP) + encoderFlags |= nsIDocumentEncoder::OutputWrap; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED) + encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS) + encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_HTML_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_W3C_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS) + encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS) + encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT) + encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT) + encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent; + + return encoderFlags; +} + +static bool +ContentTypeEncoderExists(const nsACString& aType) +{ + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aType); + + nsCOMPtr<nsIComponentRegistrar> registrar; + nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && registrar) { + bool result; + rv = registrar->IsContractIDRegistered(contractID.get(), &result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return NS_SUCCEEDED(rv) && result; + } + return false; +} + +void +WebBrowserPersistLocalDocument::DecideContentType(nsACString& aContentType) +{ + if (aContentType.IsEmpty()) { + if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) { + aContentType.Truncate(); + } + } + if (!aContentType.IsEmpty() && + !ContentTypeEncoderExists(aContentType)) { + aContentType.Truncate(); + } + if (aContentType.IsEmpty()) { + aContentType.AssignLiteral("text/html"); + } +} + +nsresult +WebBrowserPersistLocalDocument::GetDocEncoder(const nsACString& aContentType, + uint32_t aEncoderFlags, + nsIDocumentEncoder** aEncoder) +{ + nsresult rv; + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aContentType); + nsCOMPtr<nsIDocumentEncoder> encoder = + do_CreateInstance(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = encoder->NativeInit(mDocument, + NS_ConvertASCIItoUTF16(aContentType), + ConvertEncoderFlags(aEncoderFlags)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsAutoCString charSet; + rv = GetCharacterSet(charSet); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + rv = encoder->SetCharset(charSet); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + encoder.forget(aEncoder); + return NS_OK; +} + + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::WriteContent( + nsIOutputStream* aStream, + nsIWebBrowserPersistURIMap* aMap, + const nsACString& aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn, + nsIWebBrowserPersistWriteCompletion* aCompletion) +{ + NS_ENSURE_ARG_POINTER(aStream); + NS_ENSURE_ARG_POINTER(aCompletion); + nsAutoCString contentType(aRequestedContentType); + DecideContentType(contentType); + + nsCOMPtr<nsIDocumentEncoder> encoder; + nsresult rv = GetDocEncoder(contentType, aEncoderFlags, + getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aWrapColumn != 0 && (aEncoderFlags + & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) { + encoder->SetWrapColumn(aWrapColumn); + } + + nsCOMPtr<nsIURI> targetURI; + if (aMap) { + nsAutoCString targetURISpec; + rv = aMap->GetTargetBaseURI(targetURISpec); + if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) { + rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec, + /* charset: */ nullptr, /* base: */ nullptr); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); + } else if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) { + return NS_ERROR_UNEXPECTED; + } + } + rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = encoder->EncodeToStream(aStream); + aCompletion->OnFinish(this, aStream, contentType, rv); + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h new file mode 100644 index 000000000..9110d6c35 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistLocalDocument_h__ +#define WebBrowserPersistLocalDocument_h__ + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocument.h" +#include "nsIURI.h" +#include "nsIWebBrowserPersistDocument.h" + +class nsIDocumentEncoder; +class nsISHEntry; + +namespace mozilla { + +class WebBrowserPersistLocalDocument final + : public nsIWebBrowserPersistDocument +{ +public: + explicit WebBrowserPersistLocalDocument(nsIDocument* aDocument); + + const nsCString& GetCharacterSet() const; + uint32_t GetPersistFlags() const; + already_AddRefed<nsIURI> GetBaseURI() const; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT + + NS_DECL_CYCLE_COLLECTION_CLASS(WebBrowserPersistLocalDocument) + +private: + nsCOMPtr<nsIDocument> mDocument; + uint32_t mPersistFlags; + + void DecideContentType(nsACString& aContentType); + nsresult GetDocEncoder(const nsACString& aContentType, + uint32_t aEncoderFlags, + nsIDocumentEncoder** aEncoder); + already_AddRefed<nsISHEntry> GetHistory(); + + virtual ~WebBrowserPersistLocalDocument(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistLocalDocument_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp new file mode 100644 index 000000000..c7dcca861 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistRemoteDocument.h" +#include "WebBrowserPersistDocumentParent.h" +#include "WebBrowserPersistResourcesParent.h" +#include "WebBrowserPersistSerializeParent.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument, + nsIWebBrowserPersistDocument) + +WebBrowserPersistRemoteDocument +::WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, + const Attrs& aAttrs, + nsIInputStream* aPostData) +: mActor(aActor) +, mAttrs(aAttrs) +, mPostData(aPostData) +{ +} + +WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument() +{ + if (mActor) { + Unused << mActor->Send__delete__(mActor); + // That will call mActor->ActorDestroy, which calls this->ActorDestroy + // (whether or not the IPC send succeeds). + } + MOZ_ASSERT(!mActor); +} + +void +WebBrowserPersistRemoteDocument::ActorDestroy(void) +{ + mActor = nullptr; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetIsPrivate(bool* aIsPrivate) +{ + *aIsPrivate = mAttrs.isPrivate(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetDocumentURI(nsACString& aURISpec) +{ + aURISpec = mAttrs.documentURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetBaseURI(nsACString& aURISpec) +{ + aURISpec = mAttrs.baseURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetContentType(nsACString& aContentType) +{ + aContentType = mAttrs.contentType(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetCharacterSet(nsACString& aCharSet) +{ + aCharSet = mAttrs.characterSet(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetTitle(nsAString& aTitle) +{ + aTitle = mAttrs.title(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetReferrer(nsAString& aReferrer) +{ + aReferrer = mAttrs.referrer(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetContentDisposition(nsAString& aDisp) +{ + aDisp = mAttrs.contentDisposition(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetCacheKey(uint32_t* aCacheKey) +{ + *aCacheKey = mAttrs.cacheKey(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPersistFlags(uint32_t* aFlags) +{ + *aFlags = mAttrs.persistFlags(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::SetPersistFlags(uint32_t aFlags) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + if (!mActor->SendSetPersistFlags(aFlags)) { + return NS_ERROR_FAILURE; + } + mAttrs.persistFlags() = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPostData(nsIInputStream** aStream) +{ + nsCOMPtr<nsIInputStream> stream = mPostData; + stream.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + RefPtr<WebBrowserPersistResourcesParent> subActor = + new WebBrowserPersistResourcesParent(this, aVisitor); + return mActor->SendPWebBrowserPersistResourcesConstructor( + subActor.forget().take()) + ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::WriteContent( + nsIOutputStream* aStream, + nsIWebBrowserPersistURIMap* aMap, + const nsACString& aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn, + nsIWebBrowserPersistWriteCompletion* aCompletion) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + WebBrowserPersistURIMap map; + uint32_t numMappedURIs; + if (aMap) { + rv = aMap->GetTargetBaseURI(map.targetBaseURI()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aMap->GetNumMappedURIs(&numMappedURIs); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < numMappedURIs; ++i) { + WebBrowserPersistURIMapEntry& nextEntry = + *(map.mapURIs().AppendElement()); + rv = aMap->GetURIMapping(i, nextEntry.mapFrom(), nextEntry.mapTo()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + auto* subActor = new WebBrowserPersistSerializeParent(this, + aStream, + aCompletion); + nsCString requestedContentType(aRequestedContentType); // Sigh. + return mActor->SendPWebBrowserPersistSerializeConstructor( + subActor, map, requestedContentType, aEncoderFlags, aWrapColumn) + ? NS_OK : NS_ERROR_FAILURE; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h new file mode 100644 index 000000000..08d435903 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistRemoteDocument_h__ +#define WebBrowserPersistRemoteDocument_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PWebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" +#include "nsIInputStream.h" + +// This class is the XPCOM half of the glue between the +// nsIWebBrowserPersistDocument interface and a remote document; it is +// created by WebBrowserPersistDocumentParent when (and if) it +// receives the information needed to populate the interface's +// properties. +// +// This object has a normal refcounted lifetime. The corresponding +// IPC actor holds a weak reference to this class; when the last +// strong reference is released, it sends an IPC delete message and +// thereby removes that reference. + +namespace mozilla { + +class WebBrowserPersistDocumentParent; + +class WebBrowserPersistRemoteDocument final + : public nsIWebBrowserPersistDocument +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT + +private: + using Attrs = WebBrowserPersistDocumentAttrs; + WebBrowserPersistDocumentParent* mActor; + Attrs mAttrs; + nsCOMPtr<nsIInputStream> mPostData; + + friend class WebBrowserPersistDocumentParent; + WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, + const Attrs& aAttrs, + nsIInputStream* aPostData); + ~WebBrowserPersistRemoteDocument(); + + void ActorDestroy(void); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistRemoteDocument_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp new file mode 100644 index 000000000..c28f034c8 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistResourcesChild.h" + +#include "WebBrowserPersistDocumentChild.h" +#include "mozilla/dom/ContentChild.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesChild, + nsIWebBrowserPersistResourceVisitor) + +WebBrowserPersistResourcesChild::WebBrowserPersistResourcesChild() +{ +} + +WebBrowserPersistResourcesChild::~WebBrowserPersistResourcesChild() +{ +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::VisitResource(nsIWebBrowserPersistDocument *aDocument, + const nsACString& aURI) +{ + nsCString copiedURI(aURI); // Yay, XPIDL/IPDL mismatch. + SendVisitResource(copiedURI); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistDocument* aSubDocument) +{ + auto* subActor = new WebBrowserPersistDocumentChild(); + // As a consequence of how PWebBrowserPersistDocumentConstructor + // can be sent by both the parent and the child, we must pass the + // aBrowser and outerWindowID arguments here, but the values are + // ignored by the parent. In particular, the TabChild in which + // persistence started does not necessarily exist at this point; + // see bug 1203602. + if (!Manager()->Manager() + ->SendPWebBrowserPersistDocumentConstructor(subActor, nullptr, 0)) { + // NOTE: subActor is freed at this point. + return NS_ERROR_FAILURE; + } + // ...but here, IPC won't free subActor until after this returns + // to the event loop. + + // The order of these two messages will be preserved, because + // they're the same toplevel protocol and priority. + // + // With this ordering, it's always the transition out of START + // state that causes a document's parent actor to be exposed to + // XPCOM (for both parent->child and child->parent construction), + // which simplifies the lifetime management. + SendVisitDocument(subActor); + subActor->Start(aSubDocument); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::EndVisit(nsIWebBrowserPersistDocument *aDocument, + nsresult aStatus) +{ + Send__delete__(this, aStatus); + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h new file mode 100644 index 000000000..d6ab10d02 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistResourcesChild_h__ +#define WebBrowserPersistResourcesChild_h__ + +#include "mozilla/PWebBrowserPersistResourcesChild.h" + +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistResourcesChild final + : public PWebBrowserPersistResourcesChild + , public nsIWebBrowserPersistResourceVisitor +{ +public: + WebBrowserPersistResourcesChild(); + + NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR + NS_DECL_ISUPPORTS +private: + virtual ~WebBrowserPersistResourcesChild(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp new file mode 100644 index 000000000..195cabb58 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistResourcesParent.h" + +#include "nsThreadUtils.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesParent, + nsIWebBrowserPersistDocumentReceiver) + +WebBrowserPersistResourcesParent::WebBrowserPersistResourcesParent( + nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistResourceVisitor* aVisitor) +: mDocument(aDocument) +, mVisitor(aVisitor) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(aVisitor); +} + +WebBrowserPersistResourcesParent::~WebBrowserPersistResourcesParent() +{ +} + +void +WebBrowserPersistResourcesParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (aWhy != Deletion && mVisitor) { + // See comment in WebBrowserPersistDocumentParent::ActorDestroy + // (or bug 1202887) for why this is deferred. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsCOMPtr<nsIWebBrowserPersistDocument>, nsresult> + (mVisitor, &nsIWebBrowserPersistResourceVisitor::EndVisit, + mDocument, NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + } + mVisitor = nullptr; +} + +bool +WebBrowserPersistResourcesParent::Recv__delete__(const nsresult& aStatus) +{ + mVisitor->EndVisit(mDocument, aStatus); + mVisitor = nullptr; + return true; +} + +bool +WebBrowserPersistResourcesParent::RecvVisitResource(const nsCString& aURI) +{ + mVisitor->VisitResource(mDocument, aURI); + return true; +} + +bool +WebBrowserPersistResourcesParent::RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) +{ + // Don't expose the subdocument to the visitor until it's ready + // (until the actor isn't in START state). + static_cast<WebBrowserPersistDocumentParent*>(aSubDocument) + ->SetOnReady(this); + return true; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesParent::OnDocumentReady(nsIWebBrowserPersistDocument* aSubDocument) +{ + if (!mVisitor) { + return NS_ERROR_FAILURE; + } + mVisitor->VisitDocument(mDocument, aSubDocument); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesParent::OnError(nsresult aFailure) +{ + // Nothing useful to do but ignore the failed document. + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h new file mode 100644 index 000000000..9d70fd11b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistResourcesParent_h__ +#define WebBrowserPersistResourcesParent_h__ + +#include "mozilla/PWebBrowserPersistResourcesParent.h" + +#include "WebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistResourcesParent final + : public PWebBrowserPersistResourcesParent + , public nsIWebBrowserPersistDocumentReceiver +{ +public: + WebBrowserPersistResourcesParent(nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistResourceVisitor* aVisitor); + + virtual bool + RecvVisitResource(const nsCString& aURI) override; + + virtual bool + RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) override; + + virtual bool + Recv__delete__(const nsresult& aStatus) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER + NS_DECL_ISUPPORTS + +private: + // Note: even if the XPIDL didn't need mDocument for visitor + // callbacks, this object still needs to hold a strong reference + // to it to defer actor subtree deletion until after the + // visitation is finished. + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor; + + virtual ~WebBrowserPersistResourcesParent(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistResourcesParent_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp new file mode 100644 index 000000000..2b6fcbde4 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistSerializeChild.h" + +#include <algorithm> + +#include "nsThreadUtils.h" +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistSerializeChild, + nsIWebBrowserPersistWriteCompletion, + nsIWebBrowserPersistURIMap, + nsIOutputStream) + +WebBrowserPersistSerializeChild::WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap) +: mMap(aMap) +{ +} + +WebBrowserPersistSerializeChild::~WebBrowserPersistSerializeChild() +{ +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::OnFinish(nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + const nsACString& aContentType, + nsresult aStatus) +{ + MOZ_ASSERT(aStream == this); + nsCString contentType(aContentType); + Send__delete__(this, contentType, aStatus); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetNumMappedURIs(uint32_t* aNum) +{ + *aNum = static_cast<uint32_t>(mMap.mapURIs().Length()); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetURIMapping(uint32_t aIndex, + nsACString& aMapFrom, + nsACString& aMapTo) +{ + if (aIndex >= mMap.mapURIs().Length()) { + return NS_ERROR_INVALID_ARG; + } + aMapFrom = mMap.mapURIs()[aIndex].mapFrom(); + aMapTo = mMap.mapURIs()[aIndex].mapTo(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetTargetBaseURI(nsACString& aURI) +{ + aURI = mMap.targetBaseURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Close() +{ + NS_WARNING("WebBrowserPersistSerializeChild::Close()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Flush() +{ + NS_WARNING("WebBrowserPersistSerializeChild::Flush()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Write(const char* aBuf, uint32_t aCount, + uint32_t* aWritten) +{ + // Normally an nsIOutputStream would have to be thread-safe, but + // nsDocumentEncoder currently doesn't call this off the main + // thread (which also means it's difficult to test the + // thread-safety code this class doesn't yet have). + // + // This is *not* an NS_ERROR_NOT_IMPLEMENTED, because at this + // point we've probably already misused the non-thread-safe + // refcounting. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Fix this class to be thread-safe."); + + // Work around bug 1181433 by sending multiple messages if + // necessary to write the entire aCount bytes, even though + // nsIOutputStream.idl says we're allowed to do a short write. + const char* buf = aBuf; + uint32_t count = aCount; + *aWritten = 0; + while (count > 0) { + uint32_t toWrite = std::min(IPC::MAX_MESSAGE_SIZE, count); + nsTArray<uint8_t> arrayBuf; + // It would be nice if this extra copy could be avoided. + arrayBuf.AppendElements(buf, toWrite); + SendWriteData(Move(arrayBuf)); + *aWritten += toWrite; + buf += toWrite; + count -= toWrite; + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::WriteFrom(nsIInputStream* aFrom, + uint32_t aCount, + uint32_t* aWritten) +{ + NS_WARNING("WebBrowserPersistSerializeChild::WriteFrom()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::WriteSegments(nsReadSegmentFun aFun, + void* aCtx, + uint32_t aCount, + uint32_t* aWritten) +{ + NS_WARNING("WebBrowserPersistSerializeChild::WriteSegments()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::IsNonBlocking(bool* aNonBlocking) +{ + // Writes will never fail with NS_BASE_STREAM_WOULD_BLOCK, so: + *aNonBlocking = false; + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h new file mode 100644 index 000000000..ddca44ece --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistSerializeChild_h__ +#define WebBrowserPersistSerializeChild_h__ + +#include "mozilla/PWebBrowserPersistSerializeChild.h" + +#include "mozilla/PWebBrowserPersistDocument.h" +#include "nsIWebBrowserPersistDocument.h" +#include "nsIOutputStream.h" + +namespace mozilla { + +class WebBrowserPersistSerializeChild final + : public PWebBrowserPersistSerializeChild + , public nsIWebBrowserPersistWriteCompletion + , public nsIWebBrowserPersistURIMap + , public nsIOutputStream +{ +public: + explicit WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap); + + NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION + NS_DECL_NSIWEBBROWSERPERSISTURIMAP + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_ISUPPORTS +private: + WebBrowserPersistURIMap mMap; + + virtual ~WebBrowserPersistSerializeChild(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistSerializeChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp new file mode 100644 index 000000000..2f76ffb33 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "WebBrowserPersistSerializeParent.h" + +#include "nsReadableUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +WebBrowserPersistSerializeParent::WebBrowserPersistSerializeParent( + nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + nsIWebBrowserPersistWriteCompletion* aFinish) +: mDocument(aDocument) +, mStream(aStream) +, mFinish(aFinish) +, mOutputError(NS_OK) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(aStream); + MOZ_ASSERT(aFinish); +} + +WebBrowserPersistSerializeParent::~WebBrowserPersistSerializeParent() +{ +} + +bool +WebBrowserPersistSerializeParent::RecvWriteData(nsTArray<uint8_t>&& aData) +{ + if (NS_FAILED(mOutputError)) { + return true; + } + + uint32_t written = 0; + static_assert(sizeof(char) == sizeof(uint8_t), + "char must be (at least?) 8 bits"); + const char* data = reinterpret_cast<const char*>(aData.Elements()); + // nsIOutputStream::Write is allowed to return short writes. + while (written < aData.Length()) { + uint32_t writeReturn; + nsresult rv = mStream->Write(data + written, + aData.Length() - written, + &writeReturn); + if (NS_FAILED(rv)) { + mOutputError = rv; + return true; + } + written += writeReturn; + } + return true; +} + +bool +WebBrowserPersistSerializeParent::Recv__delete__(const nsCString& aContentType, + const nsresult& aStatus) +{ + if (NS_SUCCEEDED(mOutputError)) { + mOutputError = aStatus; + } + mFinish->OnFinish(mDocument, + mStream, + aContentType, + mOutputError); + mFinish = nullptr; + return true; +} + +void +WebBrowserPersistSerializeParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mFinish) { + MOZ_ASSERT(aWhy != Deletion); + // See comment in WebBrowserPersistDocumentParent::ActorDestroy + // (or bug 1202887) for why this is deferred. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsCOMPtr<nsIWebBrowserPersistDocument>, nsCOMPtr<nsIOutputStream>, + nsCString, nsresult> + (mFinish, &nsIWebBrowserPersistWriteCompletion::OnFinish, + mDocument, mStream, EmptyCString(), NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + mFinish = nullptr; + } +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h new file mode 100644 index 000000000..452feef4b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef WebBrowserPersistSerializeParent_h__ +#define WebBrowserPersistSerializeParent_h__ + +#include "mozilla/PWebBrowserPersistSerializeParent.h" + +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistSerializeParent + : public PWebBrowserPersistSerializeParent +{ +public: + WebBrowserPersistSerializeParent( + nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + nsIWebBrowserPersistWriteCompletion* aFinish); + virtual ~WebBrowserPersistSerializeParent(); + + virtual bool + RecvWriteData(nsTArray<uint8_t>&& aData) override; + + virtual bool + Recv__delete__(const nsCString& aContentType, + const nsresult& aStatus) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + +private: + // See also ...ReadParent::mDocument for the other reason this + // strong reference needs to be here. + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIOutputStream> mStream; + nsCOMPtr<nsIWebBrowserPersistWriteCompletion> mFinish; + nsresult mOutputError; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistSerializeParent_h__ diff --git a/embedding/components/webbrowserpersist/moz.build b/embedding/components/webbrowserpersist/moz.build new file mode 100644 index 000000000..0d4108388 --- /dev/null +++ b/embedding/components/webbrowserpersist/moz.build @@ -0,0 +1,49 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + 'nsCWebBrowserPersist.idl', + 'nsIWebBrowserPersist.idl', + 'nsIWebBrowserPersistable.idl', + 'nsIWebBrowserPersistDocument.idl', +] + +XPIDL_MODULE = 'webbrowserpersist' + +IPDL_SOURCES += [ + 'PWebBrowserPersistDocument.ipdl', + 'PWebBrowserPersistResources.ipdl', + 'PWebBrowserPersistSerialize.ipdl', +] + +SOURCES += [ + 'nsWebBrowserPersist.cpp', + 'WebBrowserPersistDocumentChild.cpp', + 'WebBrowserPersistDocumentParent.cpp', + 'WebBrowserPersistLocalDocument.cpp', + 'WebBrowserPersistRemoteDocument.cpp', + 'WebBrowserPersistResourcesChild.cpp', + 'WebBrowserPersistResourcesParent.cpp', + 'WebBrowserPersistSerializeChild.cpp', + 'WebBrowserPersistSerializeParent.cpp', +] + +EXPORTS.mozilla += [ + 'WebBrowserPersistDocumentChild.h', + 'WebBrowserPersistDocumentParent.h', + 'WebBrowserPersistLocalDocument.h', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/base', + '/dom/html', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl new file mode 100644 index 000000000..69f2db7ca --- /dev/null +++ b/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl @@ -0,0 +1,15 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsIWebBrowserPersist.idl" + +%{ C++ +// {7E677795-C582-4cd1-9E8D-8271B3474D2A} +#define NS_WEBBROWSERPERSIST_CID \ +{ 0x7e677795, 0xc582, 0x4cd1, { 0x9e, 0x8d, 0x82, 0x71, 0xb3, 0x47, 0x4d, 0x2a } } +#define NS_WEBBROWSERPERSIST_CONTRACTID \ +"@mozilla.org/embedding/browser/nsWebBrowserPersist;1" +%} diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl new file mode 100644 index 000000000..cd4bd8b69 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl @@ -0,0 +1,286 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsICancelable.idl" + +interface nsIURI; +interface nsIInputStream; +interface nsIDOMDocument; +interface nsIWebProgressListener; +interface nsIFile; +interface nsIChannel; +interface nsILoadContext; + +/** + * Interface for persisting DOM documents and URIs to local or remote storage. + */ +[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)] +interface nsIWebBrowserPersist : nsICancelable +{ + /** No special persistence behaviour. */ + const unsigned long PERSIST_FLAGS_NONE = 0; + /** Use cached data if present (skipping validation), else load from network */ + const unsigned long PERSIST_FLAGS_FROM_CACHE = 1; + /** Bypass the cached data. */ + const unsigned long PERSIST_FLAGS_BYPASS_CACHE = 2; + /** Ignore any redirected data (usually adverts). */ + const unsigned long PERSIST_FLAGS_IGNORE_REDIRECTED_DATA = 4; + /** Ignore IFRAME content (usually adverts). */ + const unsigned long PERSIST_FLAGS_IGNORE_IFRAMES = 8; + /** Do not run the incoming data through a content converter e.g. to decompress it */ + const unsigned long PERSIST_FLAGS_NO_CONVERSION = 16; + /** Replace existing files on the disk (use with due diligence!) */ + const unsigned long PERSIST_FLAGS_REPLACE_EXISTING_FILES = 32; + /** Don't modify or add base tags */ + const unsigned long PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS = 64; + /** Make changes to original dom rather than cloning nodes */ + const unsigned long PERSIST_FLAGS_FIXUP_ORIGINAL_DOM = 128; + /** Fix links relative to destination location (not origin) */ + const unsigned long PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION = 256; + /** Don't make any adjustments to links */ + const unsigned long PERSIST_FLAGS_DONT_FIXUP_LINKS = 512; + /** Force serialization of output (one file at a time; not concurrent) */ + const unsigned long PERSIST_FLAGS_SERIALIZE_OUTPUT = 1024; + /** Don't make any adjustments to filenames */ + const unsigned long PERSIST_FLAGS_DONT_CHANGE_FILENAMES = 2048; + /** Fail on broken inline links */ + const unsigned long PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS = 4096; + /** + * Automatically cleanup after a failed or cancelled operation, deleting all + * created files and directories. This flag does nothing for failed upload + * operations to remote servers. + */ + const unsigned long PERSIST_FLAGS_CLEANUP_ON_FAILURE = 8192; + /** + * Let the WebBrowserPersist decide whether the incoming data is encoded + * and whether it needs to go through a content converter e.g. to + * decompress it. + */ + const unsigned long PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION = 16384; + /** + * Append the downloaded data to the target file. + * This can only be used when persisting to a local file. + */ + const unsigned long PERSIST_FLAGS_APPEND_TO_FILE = 32768; + + /** + * Force relevant cookies to be sent with this load even if normally they + * wouldn't be. + */ + const unsigned long PERSIST_FLAGS_FORCE_ALLOW_COOKIES = 65536; + + /** + * Flags governing how data is fetched and saved from the network. + * It is best to set this value explicitly unless you are prepared + * to accept the default values. + */ + attribute unsigned long persistFlags; + + /** Persister is ready to save data */ + const unsigned long PERSIST_STATE_READY = 1; + /** Persister is saving data */ + const unsigned long PERSIST_STATE_SAVING = 2; + /** Persister has finished saving data */ + const unsigned long PERSIST_STATE_FINISHED = 3; + + /** + * Current state of the persister object. + */ + readonly attribute unsigned long currentState; + + /** + * Value indicating the success or failure of the persist + * operation. + * + * @throws NS_BINDING_ABORTED Operation cancelled. + * @throws NS_ERROR_FAILURE Non-specific failure. + */ + readonly attribute nsresult result; + + /** + * Callback listener for progress notifications. The object that the + * embbedder supplies may also implement nsIInterfaceRequestor and be + * prepared to return nsIAuthPrompt or other interfaces that may be required + * to download data. + * + * @see nsIAuthPrompt + * @see nsIInterfaceRequestor + */ + attribute nsIWebProgressListener progressListener; + + /** + * Save the specified URI to file. + * + * @param aURI URI to save to file. Some implementations of this interface + * may also support <CODE>nullptr</CODE> to imply the currently + * loaded URI. + * @param aCacheKey An object representing the URI in the cache or + * <CODE>nullptr</CODE>. This can be a necko cache key, + * an nsIWebPageDescriptor, or the currentDescriptor of an + * nsIWebPageDescriptor. + * @param aReferrer The referrer URI to pass with an HTTP request or + * <CODE>nullptr</CODE>. + * @param aReferrerPolicy The referrer policy for when and what to send via + * HTTP Referer header. Ignored if aReferrer is + * <CODE>nullptr</CODE>. Taken from REFERRER_POLICY + * constants in nsIHttpChannel. + * @param aPostData Post data to pass with an HTTP request or + * <CODE>nullptr</CODE>. + * @param aExtraHeaders Additional headers to supply with an HTTP request + * or <CODE>nullptr</CODE>. + * @param aFile Target file. This may be a nsIFile object or an + * nsIURI object with a file scheme or a scheme that + * supports uploading (e.g. ftp). + * @param aPrivacyContext A context from which the privacy status of this + * save operation can be determined. Must only be null + * in situations in which no such context is available + * (eg. the operation has no logical association with any + * window or document) + * + * @see nsIFile + * @see nsIURI + * @see nsIInputStream + * + * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. + */ + void saveURI(in nsIURI aURI, in nsISupports aCacheKey, + in nsIURI aReferrer, in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in string aExtraHeaders, in nsISupports aFile, + in nsILoadContext aPrivacyContext); + + /** + * @param aIsPrivate Treat the save operation as private (ie. with + * regards to networking operations and persistence + * of intermediate data, etc.) + * @see saveURI for all other parameter descriptions + */ + void savePrivacyAwareURI(in nsIURI aURI, in nsISupports aCacheKey, + in nsIURI aReferrer, in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in string aExtraHeaders, in nsISupports aFile, + in boolean aIsPrivate); + + /** + * Save a channel to a file. It must not be opened yet. + * @see saveURI + */ + void saveChannel(in nsIChannel aChannel, in nsISupports aFile); + + /** Output only the current selection as opposed to the whole document. */ + const unsigned long ENCODE_FLAGS_SELECTION_ONLY = 1; + /** + * For plaintext output. Convert html to plaintext that looks like the html. + * Implies wrap (except inside <pre>), since html wraps. + * HTML output: always do prettyprinting, ignoring existing formatting. + */ + const unsigned long ENCODE_FLAGS_FORMATTED = 2; + /** + * Output without formatting or wrapping the content. This flag + * may be used to preserve the original formatting as much as possible. + */ + const unsigned long ENCODE_FLAGS_RAW = 4; + /** Output only the body section, no HTML tags. */ + const unsigned long ENCODE_FLAGS_BODY_ONLY = 8; + /** Wrap even if when not doing formatted output (e.g. for text fields). */ + const unsigned long ENCODE_FLAGS_PREFORMATTED = 16; + /** Wrap documents at the specified column. */ + const unsigned long ENCODE_FLAGS_WRAP = 32; + /** + * For plaintext output. Output for format flowed (RFC 2646). This is used + * when converting to text for mail sending. This differs just slightly + * but in an important way from normal formatted, and that is that + * lines are space stuffed. This can't (correctly) be done later. + */ + const unsigned long ENCODE_FLAGS_FORMAT_FLOWED = 64; + /** Convert links to absolute links where possible. */ + const unsigned long ENCODE_FLAGS_ABSOLUTE_LINKS = 128; + + /** + * Attempt to encode entities standardized at W3C (HTML, MathML, etc). + * This is a catch-all flag for documents with mixed contents. Beware of + * interoperability issues. See below for other flags which might likely + * do what you want. + */ + const unsigned long ENCODE_FLAGS_ENCODE_W3C_ENTITIES = 256; + + /** + * Output with carriage return line breaks. May also be combined with + * ENCODE_FLAGS_LF_LINEBREAKS and if neither is specified, the platform + * default format is used. + */ + const unsigned long ENCODE_FLAGS_CR_LINEBREAKS = 512; + /** + * Output with linefeed line breaks. May also be combined with + * ENCODE_FLAGS_CR_LINEBREAKS and if neither is specified, the platform + * default format is used. + */ + const unsigned long ENCODE_FLAGS_LF_LINEBREAKS = 1024; + /** For plaintext output. Output the content of noscript elements. */ + const unsigned long ENCODE_FLAGS_NOSCRIPT_CONTENT = 2048; + /** For plaintext output. Output the content of noframes elements. */ + const unsigned long ENCODE_FLAGS_NOFRAMES_CONTENT = 4096; + + /** + * Encode basic entities, e.g. output instead of character code 0xa0. + * The basic set is just & < > " for interoperability + * with older products that don't support α and friends. + */ + const unsigned long ENCODE_FLAGS_ENCODE_BASIC_ENTITIES = 8192; + /** + * Encode Latin1 entities. This includes the basic set and + * accented letters between 128 and 255. + */ + const unsigned long ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES = 16384; + /** + * Encode HTML4 entities. This includes the basic set, accented + * letters, greek letters and certain special markup symbols. + */ + const unsigned long ENCODE_FLAGS_ENCODE_HTML_ENTITIES = 32768; + + /** + * Save the specified DOM document to file and optionally all linked files + * (e.g. images, CSS, JS & subframes). Do not call this method until the + * document has finished loading! + * + * @param aDocument Document to save to file. Some implementations of + * this interface may also support <CODE>nullptr</CODE> + * to imply the currently loaded document. Can be an + * nsIWebBrowserPersistDocument or nsIDOMDocument. + * @param aFile Target local file. This may be a nsIFile object or an + * nsIURI object with a file scheme or a scheme that + * supports uploading (e.g. ftp). + * @param aDataPath Path to directory where URIs linked to the document + * are saved or nullptr if no linked URIs should be saved. + * This may be a nsIFile object or an nsIURI object + * with a file scheme. + * @param aOutputContentType The desired MIME type format to save the + * document and all subdocuments into or nullptr to use + * the default behaviour. + * @param aEncodingFlags Flags to pass to the encoder. + * @param aWrapColumn For text documents, indicates the desired width to + * wrap text at. Parameter is ignored if wrapping is not + * specified by the encoding flags. + * + * @see nsIWebBrowserPersistDocument + * @see nsIWebBrowserPersistable + * @see nsIFile + * @see nsIURI + * + * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. + */ + void saveDocument(in nsISupports aDocument, + in nsISupports aFile, in nsISupports aDataPath, + in string aOutputContentType, in unsigned long aEncodingFlags, + in unsigned long aWrapColumn); + + /** + * Cancels the current operation. The caller is responsible for cleaning up + * partially written files or directories. This has the same effect as calling + * cancel with an argument of NS_BINDING_ABORTED. + */ + void cancelSave(); +}; diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl new file mode 100644 index 000000000..ba5bea8b2 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl @@ -0,0 +1,197 @@ +/* -*- Mode: IDL; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +interface nsIDOMDocument; +interface nsIInputStream; +interface nsIOutputStream; +interface nsITabParent; +interface nsIWebBrowserPersistResourceVisitor; +interface nsIWebBrowserPersistWriteCompletion; + +/** + * Interface for the URI-mapping information that can be supplied when + * serializing the DOM of an nsIWebBrowserPersistDocument. + * + * @see nsIWebBrowserPersistDocument + */ +[scriptable, uuid(d52e8b93-2771-45e8-a5b0-6e12b667046b)] +interface nsIWebBrowserPersistURIMap : nsISupports +{ + /** + * The number of URI mappings. + */ + readonly attribute unsigned long numMappedURIs; + + /** + * Obtain the URI mapping at the given index, which must be less than + * numMappedURIs, as a pair of URI spec strings. + */ + void getURIMapping(in unsigned long aIndex, + out AUTF8String aMapFrom, + out AUTF8String aMapTo); + + /** + * The spec of the base URI that the document will have after it is + * serialized. + */ + readonly attribute AUTF8String targetBaseURI; +}; + +/** + * Interface representing a document that can be serialized with + * nsIWebBrowserPersist; it may or may not be in this process. Some + * information is exposed as attributes, which may or may not reflect + * changes made to the underlying document; most of these are + * self-explanatory from their names and types. + */ +[scriptable, uuid(74aa4918-5d15-46b6-9ccf-74f9696d721d)] +interface nsIWebBrowserPersistDocument : nsISupports +{ + readonly attribute boolean isPrivate; + readonly attribute AUTF8String documentURI; + readonly attribute AUTF8String baseURI; + readonly attribute ACString contentType; + readonly attribute ACString characterSet; + readonly attribute AString title; + readonly attribute AString referrer; + readonly attribute AString contentDisposition; + readonly attribute nsIInputStream postData; + + /** + * The cache key. Unlike in nsISHEntry, where it's wrapped in an + * nsISupportsPRUint32, this is just the integer. + */ + readonly attribute unsigned long cacheKey; + + /** + * This attribute is set by nsIWebBrowserPersist implementations to + * propagate persist flags that apply to the DOM traversal and + * serialization (rather than to managing file I/O). + */ + attribute unsigned long persistFlags; + + /** + * Walk the DOM searching for external resources needed to render it. + * The visitor callbacks may be called either before or after + * readResources returns. + * + * @see nsIWebBrowserPersistResourceVisitor + */ + void readResources(in nsIWebBrowserPersistResourceVisitor aVisitor); + + /** + * Serialize the document's DOM. + * + * @param aStream The output stream to write the document to. + * + * @param aURIMap Optional; specifies URI rewriting to perform on + * external references (as read by readResources). + * If given, also causes relative hyperlinks to be + * converted to absolute in the written text. + * + * @param aRequestedContentType + * The desired MIME type to save the document as; + * optional and defaults to the document's type. + * (If no encoder exists for that type, "text/html" + * is used instead.) + * + * @param aEncoderFlags Flags to pass to the encoder. + * + * @param aWrapColumn Desired text width, ignored if wrapping is not + * specified by the encoding flags, or if 0. + * + * @param aCompletion Callback invoked when writing is complete. + * It may be called either before or after writeContent + * returns. + * + * @see nsIDocumentEncoder + */ + void writeContent(in nsIOutputStream aStream, + in nsIWebBrowserPersistURIMap aURIMap, + in ACString aRequestedContentType, + in unsigned long aEncoderFlags, + in unsigned long aWrapColumn, + in nsIWebBrowserPersistWriteCompletion aCompletion); +}; + +/** + * Asynchronous visitor that receives external resources linked by an + * nsIWebBrowserPersistDocument and which are needed to render the + * document. + */ +[scriptable, uuid(8ce37706-b7d3-481a-be68-54f174fc0d0a)] +interface nsIWebBrowserPersistResourceVisitor : nsISupports +{ + /** + * Indicates a resource that is not a document; e.g., an image, script, + * or stylesheet. + * + * @param aDocument The document containing the reference. + * @param aURI The absolute URI spec for the referenced resource. + */ + void visitResource(in nsIWebBrowserPersistDocument aDocument, + in AUTF8String aURI); + /** + * Indicates a subdocument resource; e.g., a frame or iframe. + * + * @param aDocument The document containing the reference. + * @param aSubDocument The referenced document. + */ + void visitDocument(in nsIWebBrowserPersistDocument aDocument, + in nsIWebBrowserPersistDocument aSubDocument); + + /** + * Indicates that the document traversal is complete. + * + * @param aDocument The document that was being traversed. + * @param aStatus Indicates whether the traversal encountered an error. + */ + void endVisit(in nsIWebBrowserPersistDocument aDocument, + in nsresult aStatus); +}; + +/** + * Asynchronous callback for when nsIWebBrowserPersistDocument is finished + * serializing the document's DOM. + */ +[scriptable, function, uuid(a07e6892-38ae-4207-8340-7fa6ec446ed6)] +interface nsIWebBrowserPersistWriteCompletion : nsISupports +{ + /** + * Indicates that serialization is finished. + * + * @param aDocument The document that was being serialized. + * + * @param aStream The stream that was being written to. If it + * needs to be closed, the callback must do that; + * the serialization process leaves it open. + * + * @param aContentType The content type with which the document was + * actually serialized; this may be useful to set + * metadata on the result, or if uploading it. + * + * @param aStatus Indicates whether serialization encountered an error. + */ + void onFinish(in nsIWebBrowserPersistDocument aDocument, + in nsIOutputStream aStream, + in ACString aContentType, + in nsresult aStatus); +}; + +/** + * Asynchronous callback for creating a persistable document from some + * other object. + * + * @see nsIWebBrowserPersistable. + */ +[scriptable, uuid(321e3174-594f-4036-b7be-791b821bd376)] +interface nsIWebBrowserPersistDocumentReceiver : nsISupports +{ + void onDocumentReady(in nsIWebBrowserPersistDocument aDocument); + void onError(in nsresult aFailure); +}; diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl new file mode 100644 index 000000000..39ea6b33b --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl @@ -0,0 +1,41 @@ +/* -*- Mode: IDL; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +interface nsIWebBrowserPersistDocumentReceiver; + +/** + * Interface for objects which represent a document that can be + * serialized with nsIWebBrowserPersist. This interface is + * asynchronous because the actual document can be in another process + * (e.g., if this object is an nsFrameLoader for an out-of-process + * frame). + * + * Warning: this is currently implemented only by nsFrameLoader, and + * may change in the future to become more frame-loader-specific or be + * merged into nsIFrameLoader. See bug 1101100 comment #34. + * + * @see nsIWebBrowserPersistDocumentReceiver + * @see nsIWebBrowserPersistDocument + * @see nsIWebBrowserPersist + * + * @param aOuterWindowID + * The outer window ID of the subframe we'd like to persist. + * If set at 0, nsIWebBrowserPersistable will attempt to persist + * the top-level document. If the outer window ID is for a subframe + * that does not exist, or is not held beneath the nsIWebBrowserPersistable, + * aRecv's onError method will be called with NS_ERROR_NO_CONTENT. + * @param aRecv + * The nsIWebBrowserPersistDocumentReceiver is a callback that + * will be fired once the document is ready for persisting. + */ +[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)] +interface nsIWebBrowserPersistable : nsISupports +{ + void startPersistence(in unsigned long long aOuterWindowID, + in nsIWebBrowserPersistDocumentReceiver aRecv); +}; diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp new file mode 100644 index 000000000..de85a59bb --- /dev/null +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -0,0 +1,2812 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "mozilla/ArrayUtils.h" + +#include "nspr.h" + +#include "nsIFileStreams.h" // New Necko file streams +#include <algorithm> + +#include "nsAutoPtr.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsComponentManagerUtils.h" +#include "nsIComponentRegistrar.h" +#include "nsIStorageStream.h" +#include "nsISeekableStream.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIEncodedChannel.h" +#include "nsIUploadChannel.h" +#include "nsICacheInfoChannel.h" +#include "nsIFileChannel.h" +#include "nsEscape.h" +#include "nsUnicharUtils.h" +#include "nsIStringEnumerator.h" +#include "nsCRT.h" +#include "nsContentCID.h" +#include "nsStreamUtils.h" + +#include "nsCExternalHandlerService.h" + +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsIWebProgressListener.h" +#include "nsIAuthPrompt.h" +#include "nsIPrompt.h" +#include "nsISHEntry.h" +#include "nsIWebPageDescriptor.h" +#include "nsIFormControl.h" +#include "nsContentUtils.h" + +#include "nsIImageLoadingContent.h" + +#include "ftpCore.h" +#include "nsITransport.h" +#include "nsISocketTransport.h" +#include "nsIStringBundle.h" +#include "nsIProtocolHandler.h" + +#include "nsIWebBrowserPersistable.h" +#include "nsWebBrowserPersist.h" +#include "WebBrowserPersistLocalDocument.h" + +#include "nsIContent.h" +#include "nsIMIMEInfo.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSharedElement.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// Buffer file writes in 32kb chunks +#define BUFFERED_OUTPUT_SIZE (1024 * 32) + +struct nsWebBrowserPersist::WalkData +{ + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mDataPath; +}; + +// Information about a DOM document +struct nsWebBrowserPersist::DocData +{ + nsCOMPtr<nsIURI> mBaseURI; + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIURI> mFile; + nsCString mCharset; +}; + +// Information about a URI +struct nsWebBrowserPersist::URIData +{ + bool mNeedsPersisting; + bool mSaved; + bool mIsSubFrame; + bool mDataPathIsRelative; + bool mNeedsFixup; + nsString mFilename; + nsString mSubFrameExt; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mDataPath; + nsCOMPtr<nsIURI> mRelativeDocumentURI; + nsCString mRelativePathToData; + nsCString mCharset; + + nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut); +}; + +// Information about the output stream +struct nsWebBrowserPersist::OutputData +{ + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mOriginalLocation; + nsCOMPtr<nsIOutputStream> mStream; + int64_t mSelfProgress; + int64_t mSelfProgressMax; + bool mCalcFileExt; + + OutputData(nsIURI *aFile, nsIURI *aOriginalLocation, bool aCalcFileExt) : + mFile(aFile), + mOriginalLocation(aOriginalLocation), + mSelfProgress(0), + mSelfProgressMax(10000), + mCalcFileExt(aCalcFileExt) + { + } + ~OutputData() + { + if (mStream) + { + mStream->Close(); + } + } +}; + +struct nsWebBrowserPersist::UploadData +{ + nsCOMPtr<nsIURI> mFile; + int64_t mSelfProgress; + int64_t mSelfProgressMax; + + explicit UploadData(nsIURI *aFile) : + mFile(aFile), + mSelfProgress(0), + mSelfProgressMax(10000) + { + } +}; + +struct nsWebBrowserPersist::CleanupData +{ + nsCOMPtr<nsIFile> mFile; + // Snapshot of what the file actually is at the time of creation so that if + // it transmutes into something else later on it can be ignored. For example, + // catch files that turn into dirs or vice versa. + bool mIsDirectory; +}; + +class nsWebBrowserPersist::OnWalk final + : public nsIWebBrowserPersistResourceVisitor +{ +public: + OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath) + : mParent(aParent) + , mFile(aFile) + , mDataPath(aDataPath) + { } + + NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR + NS_DECL_ISUPPORTS +private: + RefPtr<nsWebBrowserPersist> mParent; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIFile> mDataPath; + + virtual ~OnWalk() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk, + nsIWebBrowserPersistResourceVisitor) + +class nsWebBrowserPersist::OnWrite final + : public nsIWebBrowserPersistWriteCompletion +{ +public: + OnWrite(nsWebBrowserPersist* aParent, + nsIURI* aFile, + nsIFile* aLocalFile) + : mParent(aParent) + , mFile(aFile) + , mLocalFile(aLocalFile) + { } + + NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION + NS_DECL_ISUPPORTS +private: + RefPtr<nsWebBrowserPersist> mParent; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIFile> mLocalFile; + + virtual ~OnWrite() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite, + nsIWebBrowserPersistWriteCompletion) + +class nsWebBrowserPersist::FlatURIMap final + : public nsIWebBrowserPersistURIMap +{ +public: + explicit FlatURIMap(const nsACString& aTargetBase) + : mTargetBase(aTargetBase) { } + + void Add(const nsACString& aMapFrom, const nsACString& aMapTo) { + mMapFrom.AppendElement(aMapFrom); + mMapTo.AppendElement(aMapTo); + } + + NS_DECL_NSIWEBBROWSERPERSISTURIMAP + NS_DECL_ISUPPORTS + +private: + nsTArray<nsCString> mMapFrom; + nsTArray<nsCString> mMapTo; + nsCString mTargetBase; + + virtual ~FlatURIMap() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap) + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum) +{ + MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length()); + *aNum = mMapTo.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase) +{ + aTargetBase = mTargetBase; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex, + nsACString& aMapFrom, + nsACString& aMapTo) +{ + MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length()); + if (aIndex >= mMapTo.Length()) { + return NS_ERROR_INVALID_ARG; + } + aMapFrom = mMapFrom[aIndex]; + aMapTo = mMapTo[aIndex]; + return NS_OK; +} + + +// Maximum file length constant. The max file name length is +// volume / server dependent but it is difficult to obtain +// that information. Instead this constant is a reasonable value that +// modern systems should able to cope with. +const uint32_t kDefaultMaxFilenameLength = 64; + +// Default flags for persistence +const uint32_t kDefaultPersistFlags = + nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | + nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES; + +// String bundle where error messages come from +const char *kWebBrowserPersistStringBundle = + "chrome://global/locale/nsWebBrowserPersist.properties"; + +nsWebBrowserPersist::nsWebBrowserPersist() : + mCurrentDataPathIsRelative(false), + mCurrentThingsToPersist(0), + mFirstAndOnlyUse(true), + mSavingDocument(false), + mCancel(false), + mCompleted(false), + mStartSaving(false), + mReplaceExisting(true), + mSerializingOutput(false), + mIsPrivate(false), + mPersistFlags(kDefaultPersistFlags), + mPersistResult(NS_OK), + mTotalCurrentProgress(0), + mTotalMaxProgress(0), + mWrapColumn(72), + mEncodingFlags(0) +{ +} + +nsWebBrowserPersist::~nsWebBrowserPersist() +{ + Cleanup(); +} + +//***************************************************************************** +// nsWebBrowserPersist::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(nsWebBrowserPersist) +NS_IMPL_RELEASE(nsWebBrowserPersist) + +NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsICancelable) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) +NS_INTERFACE_MAP_END + + +//***************************************************************************** +// nsWebBrowserPersist::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID & aIID, void **aIFace) +{ + NS_ENSURE_ARG_POINTER(aIFace); + + *aIFace = nullptr; + + nsresult rv = QueryInterface(aIID, aIFace); + if (NS_SUCCEEDED(rv)) + { + return rv; + } + + if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) + || aIID.Equals(NS_GET_IID(nsIPrompt)))) + { + mProgressListener->QueryInterface(aIID, aIFace); + if (*aIFace) + return NS_OK; + } + + nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener); + if (req) + { + return req->GetInterface(aIID, aIFace); + } + + return NS_ERROR_NO_INTERFACE; +} + + +//***************************************************************************** +// nsWebBrowserPersist::nsIWebBrowserPersist +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t *aPersistFlags) +{ + NS_ENSURE_ARG_POINTER(aPersistFlags); + *aPersistFlags = mPersistFlags; + return NS_OK; +} +NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags) +{ + mPersistFlags = aPersistFlags; + mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES) ? true : false; + mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT) ? true : false; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t *aCurrentState) +{ + NS_ENSURE_ARG_POINTER(aCurrentState); + if (mCompleted) + { + *aCurrentState = PERSIST_STATE_FINISHED; + } + else if (mFirstAndOnlyUse) + { + *aCurrentState = PERSIST_STATE_SAVING; + } + else + { + *aCurrentState = PERSIST_STATE_READY; + } + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPersistResult; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener( + nsIWebProgressListener * *aProgressListener) +{ + NS_ENSURE_ARG_POINTER(aProgressListener); + *aProgressListener = mProgressListener; + NS_IF_ADDREF(*aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener( + nsIWebProgressListener * aProgressListener) +{ + mProgressListener = aProgressListener; + mProgressListener2 = do_QueryInterface(aProgressListener); + mEventSink = do_GetInterface(aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SaveURI( + nsIURI *aURI, nsISupports *aCacheKey, + nsIURI *aReferrer, uint32_t aReferrerPolicy, + nsIInputStream *aPostData, const char *aExtraHeaders, + nsISupports *aFile, nsILoadContext* aPrivacyContext) +{ + return SavePrivacyAwareURI(aURI, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, aFile, + aPrivacyContext && aPrivacyContext->UsePrivateBrowsing()); +} + +NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI( + nsIURI *aURI, nsISupports *aCacheKey, + nsIURI *aReferrer, uint32_t aReferrerPolicy, + nsIInputStream *aPostData, const char *aExtraHeaders, + nsISupports *aFile, bool aIsPrivate) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + nsCOMPtr<nsIURI> fileAsURI; + nsresult rv; + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + // SaveURI doesn't like broken uris. + mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; + rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, fileAsURI, false, aIsPrivate); + return NS_FAILED(rv) ? rv : NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SaveChannel( + nsIChannel *aChannel, nsISupports *aFile) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + nsCOMPtr<nsIURI> fileAsURI; + nsresult rv; + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + rv = aChannel->GetURI(getter_AddRefs(mURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // SaveURI doesn't like broken uris. + mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; + rv = SaveChannelInternal(aChannel, fileAsURI, false); + return NS_FAILED(rv) ? rv : NS_OK; +} + + +NS_IMETHODIMP nsWebBrowserPersist::SaveDocument( + nsISupports *aDocument, nsISupports *aFile, nsISupports *aDataPath, + const char *aOutputContentType, uint32_t aEncodingFlags, uint32_t aWrapColumn) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + // We need a STATE_IS_NETWORK start/stop pair to bracket the + // notification callbacks. For a whole document we generate those + // here and in EndDownload(), but for the single-request methods + // that's done in On{Start,Stop}Request instead. + mSavingDocument = true; + + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aFile); + + nsCOMPtr<nsIURI> fileAsURI; + nsCOMPtr<nsIURI> datapathAsURI; + nsresult rv; + + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + if (aDataPath) + { + rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + } + + mWrapColumn = aWrapColumn; + mEncodingFlags = aEncodingFlags; + + if (aOutputContentType) + { + mContentType.AssignASCII(aOutputContentType); + } + + // State start notification + if (mProgressListener) { + mProgressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_START + | nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); + } + + nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument); + if (!doc) { + nsCOMPtr<nsIDocument> localDoc = do_QueryInterface(aDocument); + if (localDoc) { + doc = new mozilla::WebBrowserPersistLocalDocument(localDoc); + } else { + rv = NS_ERROR_NO_INTERFACE; + } + } + if (doc) { + rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI); + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, mURI); + EndDownload(rv); + } + return rv; +} + +NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) +{ + mCancel = true; + EndDownload(aReason); + return NS_OK; +} + + +NS_IMETHODIMP nsWebBrowserPersist::CancelSave() +{ + return Cancel(NS_BINDING_ABORTED); +} + + +nsresult +nsWebBrowserPersist::StartUpload(nsIStorageStream *storStream, + nsIURI *aDestinationURI, const nsACString &aContentType) +{ + // setup the upload channel if the destination is not local + nsCOMPtr<nsIInputStream> inputstream; + nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream)); + NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + return StartUpload(inputstream, aDestinationURI, aContentType); +} + +nsresult +nsWebBrowserPersist::StartUpload(nsIInputStream *aInputStream, + nsIURI *aDestinationURI, const nsACString &aContentType) +{ + nsCOMPtr<nsIChannel> destChannel; + CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel)); + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(destChannel)); + NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE); + + // Set the upload stream + // NOTE: ALL data must be available in "inputstream" + nsresult rv = uploadChannel->SetUploadStream(aInputStream, aContentType, -1); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + rv = destChannel->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // add this to the upload list + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel); + mUploadList.Put(keyPtr, new UploadData(aDestinationURI)); + + return NS_OK; +} + +void +nsWebBrowserPersist::SerializeNextFile() +{ + nsresult rv = NS_OK; + MOZ_ASSERT(mWalkStack.Length() == 0); + + // First, handle gathered URIs. + // Count how many URIs in the URI map require persisting + uint32_t urisToPersist = 0; + if (mURIMap.Count() > 0) { + // This is potentially O(n^2), when taking into account the + // number of times this method is called. If it becomes a + // bottleneck, the count of not-yet-persisted URIs could be + // maintained separately. + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + if (data->mNeedsPersisting && !data->mSaved) { + urisToPersist++; + } + } + } + + if (urisToPersist > 0) { + // Persist each file in the uri map. The document(s) + // will be saved after the last one of these is saved. + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + + if (!data->mNeedsPersisting || data->mSaved) { + continue; + } + + nsresult rv; + + // Create a URI from the key. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), iter.Key(), + data->mCharset.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // Make a URI to save the data to. + nsCOMPtr<nsIURI> fileAsURI; + rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = AppendPathToURI(fileAsURI, data->mFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // The Referrer Policy doesn't matter here since the referrer is + // nullptr. + rv = SaveURIInternal(uri, nullptr, nullptr, + mozilla::net::RP_Default, nullptr, nullptr, + fileAsURI, true, mIsPrivate); + // If SaveURIInternal fails, then it will have called EndDownload, + // which means that |data| is no longer valid memory. We MUST bail. + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (rv == NS_OK) { + // Store the actual object because once it's persisted this + // will be fixed up with the right file extension. + data->mFile = fileAsURI; + data->mSaved = true; + } else { + data->mNeedsFixup = false; + } + + if (mSerializingOutput) { + break; + } + } + } + + // If there are downloads happening, wait until they're done; the + // OnStopRequest handler will call this method again. + if (mOutputMap.Count() > 0) { + return; + } + + // If serializing, also wait until last upload is done. + if (mSerializingOutput && mUploadList.Count() > 0) { + return; + } + + // If there are also no more documents, then we're done. + if (mDocList.Length() == 0) { + // ...or not quite done, if there are still uploads. + if (mUploadList.Count() > 0) { + return; + } + // Finish and clean things up. Defer this because the caller + // may have been expecting to use the listeners that that + // method will clear. + NS_DispatchToCurrentThread(NewRunnableMethod(this, + &nsWebBrowserPersist::FinishDownload)); + return; + } + + // There are no URIs to save, so just save the next document. + mStartSaving = true; + mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0)); + mDocList.RemoveElementAt(0); // O(n^2) but probably doesn't matter. + MOZ_ASSERT(docData); + if (!docData) { + EndDownload(NS_ERROR_FAILURE); + return; + } + + mCurrentBaseURI = docData->mBaseURI; + mCurrentCharset = docData->mCharset; + mTargetBaseURI = docData->mFile; + + // Save the document, fixing it up with the new URIs as we do + + nsAutoCString targetBaseSpec; + if (mTargetBaseURI) { + rv = mTargetBaseURI->GetSpec(targetBaseSpec); + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, nullptr); + EndDownload(rv); + return; + } + } + + // mFlatURIMap must be rebuilt each time through SerializeNextFile, as + // mTargetBaseURI is used to create the relative URLs and will be different + // with each serialized document. + RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString mapTo; + nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo); + if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { + flatMap->Add(iter.Key(), mapTo); + } + } + mFlatURIMap = flatMap.forget(); + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile)); + if (localFile) { + // if we're not replacing an existing file but the file + // exists, something is wrong + bool fileExists = false; + rv = localFile->Exists(&fileExists); + if (NS_SUCCEEDED(rv) && !mReplaceExisting && fileExists) { + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + return; + } + } + nsCOMPtr<nsIOutputStream> outputStream; + rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream)); + if (NS_SUCCEEDED(rv) && !outputStream) { + rv = NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + return; + } + + RefPtr<OnWrite> finish = new OnWrite(this, docData->mFile, localFile); + rv = docData->mDocument->WriteContent(outputStream, + mFlatURIMap, + NS_ConvertUTF16toUTF8(mContentType), + mEncodingFlags, + mWrapColumn, + finish); + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + } +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc, + nsIOutputStream *aStream, + const nsACString& aContentType, + nsresult aStatus) +{ + nsresult rv = aStatus; + + if (NS_FAILED(rv)) { + mParent->SendErrorStatusChange(false, rv, nullptr, mFile); + mParent->EndDownload(rv); + return NS_OK; + } + if (!mLocalFile) { + nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream)); + if (storStream) { + aStream->Close(); + rv = mParent->StartUpload(storStream, mFile, aContentType); + if (NS_FAILED(rv)) { + mParent->SendErrorStatusChange(false, rv, nullptr, mFile); + mParent->EndDownload(rv); + } + // Either we failed and we're done, or we're uploading and + // the OnStopRequest callback is responsible for the next + // SerializeNextFile(). + return NS_OK; + } + } + NS_DispatchToCurrentThread(NewRunnableMethod(mParent, + &nsWebBrowserPersist::SerializeNextFile)); + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowserPersist::nsIRequestObserver +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest( + nsIRequest* request, nsISupports *ctxt) +{ + if (mProgressListener) + { + uint32_t stateFlags = nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_REQUEST; + if (!mSavingDocument) { + stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; + } + mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK); + } + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + + // NOTE: This code uses the channel as a hash key so it will not + // recognize redirected channels because the key is not the same. + // When that happens we remove and add the data entry to use the + // new channel as the hash key. + if (!data) + { + UploadData *upData = mUploadList.Get(keyPtr); + if (!upData) + { + // Redirect? Try and fixup the output table + nsresult rv = FixRedirectedChannelEntry(channel); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Should be able to find the data after fixup unless redirects + // are disabled. + data = mOutputMap.Get(keyPtr); + if (!data) + { + return NS_ERROR_FAILURE; + } + } + } + + if (data && data->mFile) + { + // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags, + // try to determine whether this channel needs to apply Content-Encoding + // conversions. + NS_ASSERTION(!((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) && + (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)), + "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set"); + if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) + SetApplyConversionIfNeeded(channel); + + if (data->mCalcFileExt && !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)) + { + // this is the first point at which the server can tell us the mimetype + CalculateAndAppendFileExt(data->mFile, channel, data->mOriginalLocation); + + // now make filename conformant and unique + CalculateUniqueFilename(data->mFile); + } + + // compare uris and bail before we add to output map if they are equal + bool isEqual = false; + if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual)) + && isEqual) + { + // remove from output map + mOutputMap.Remove(keyPtr); + + // cancel; we don't need to know any more + // stop request will get called + request->Cancel(NS_BINDING_ABORTED); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest( + nsIRequest* request, nsISupports *ctxt, nsresult status) +{ + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (data) { + if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) { + SendErrorStatusChange(true, status, request, data->mFile); + } + + // This will automatically close the output stream + mOutputMap.Remove(keyPtr); + } else { + // if we didn't find the data in mOutputMap, try mUploadList + UploadData *upData = mUploadList.Get(keyPtr); + if (upData) { + mUploadList.Remove(keyPtr); + } + } + + // Do more work. + SerializeNextFile(); + + if (mProgressListener) { + uint32_t stateFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_REQUEST; + if (!mSavingDocument) { + stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; + } + mProgressListener->OnStateChange(nullptr, request, stateFlags, status); + } + + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowserPersist::nsIStreamListener +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowserPersist::OnDataAvailable( + nsIRequest* request, nsISupports *aContext, nsIInputStream *aIStream, + uint64_t aOffset, uint32_t aLength) +{ + bool cancel = mCancel; + if (!cancel) + { + nsresult rv = NS_OK; + uint32_t bytesRemaining = aLength; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (!data) { + // might be uploadData; consume necko's buffer and bail... + uint32_t n; + return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n); + } + + bool readError = true; + + // Make the output stream + if (!data->mStream) + { + rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream)); + if (NS_FAILED(rv)) + { + readError = false; + cancel = true; + } + } + + // Read data from the input and write to the output + char buffer[8192]; + uint32_t bytesRead; + while (!cancel && bytesRemaining) + { + readError = true; + rv = aIStream->Read(buffer, + std::min(uint32_t(sizeof(buffer)), bytesRemaining), + &bytesRead); + if (NS_SUCCEEDED(rv)) + { + readError = false; + // Write out the data until something goes wrong, or, it is + // all written. We loop because for some errors (e.g., disk + // full), we get NS_OK with some bytes written, then an error. + // So, we want to write again in that case to get the actual + // error code. + const char *bufPtr = buffer; // Where to write from. + while (NS_SUCCEEDED(rv) && bytesRead) + { + uint32_t bytesWritten = 0; + rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten); + if (NS_SUCCEEDED(rv)) + { + bytesRead -= bytesWritten; + bufPtr += bytesWritten; + bytesRemaining -= bytesWritten; + // Force an error if (for some reason) we get NS_OK but + // no bytes written. + if (!bytesWritten) + { + rv = NS_ERROR_FAILURE; + cancel = true; + } + } + else + { + // Disaster - can't write out the bytes - disk full / permission? + cancel = true; + } + } + } + else + { + // Disaster - can't read the bytes - broken link / file error? + cancel = true; + } + } + + int64_t channelContentLength = -1; + if (!cancel && + NS_SUCCEEDED(channel->GetContentLength(&channelContentLength))) + { + // if we get -1 at this point, we didn't get content-length header + // assume that we got all of the data and push what we have; + // that's the best we can do now + if ((-1 == channelContentLength) || + ((channelContentLength - (aOffset + aLength)) == 0)) + { + NS_WARNING_ASSERTION( + channelContentLength != -1, + "nsWebBrowserPersist::OnDataAvailable() no content length " + "header, pushing what we have"); + // we're done with this pass; see if we need to do upload + nsAutoCString contentType; + channel->GetContentType(contentType); + // if we don't have the right type of output stream then it's a local file + nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream)); + if (storStream) + { + data->mStream->Close(); + data->mStream = nullptr; // null out stream so we don't close it later + rv = StartUpload(storStream, data->mFile, contentType); + if (NS_FAILED(rv)) + { + readError = false; + cancel = true; + } + } + } + } + + // Notify listener if an error occurred. + if (cancel) + { + SendErrorStatusChange(readError, rv, + readError ? request : nullptr, data->mFile); + } + } + + // Cancel reading? + if (cancel) + { + EndDownload(NS_BINDING_ABORTED); + } + + return NS_OK; +} + + +//***************************************************************************** +// nsWebBrowserPersist::nsIProgressEventSink +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::OnProgress( + nsIRequest *request, nsISupports *ctxt, int64_t aProgress, + int64_t aProgressMax) +{ + if (!mProgressListener) + { + return NS_OK; + } + + // Store the progress of this request + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (data) + { + data->mSelfProgress = aProgress; + data->mSelfProgressMax = aProgressMax; + } + else + { + UploadData *upData = mUploadList.Get(keyPtr); + if (upData) + { + upData->mSelfProgress = aProgress; + upData->mSelfProgressMax = aProgressMax; + } + } + + // Notify listener of total progress + CalcTotalProgress(); + if (mProgressListener2) + { + mProgressListener2->OnProgressChange64(nullptr, request, aProgress, + aProgressMax, mTotalCurrentProgress, mTotalMaxProgress); + } + else + { + // have to truncate 64-bit to 32bit + mProgressListener->OnProgressChange(nullptr, request, uint64_t(aProgress), + uint64_t(aProgressMax), mTotalCurrentProgress, mTotalMaxProgress); + } + + // If our progress listener implements nsIProgressEventSink, + // forward the notification + if (mEventSink) + { + mEventSink->OnProgress(request, ctxt, aProgress, aProgressMax); + } + + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::OnStatus( + nsIRequest *request, nsISupports *ctxt, nsresult status, + const char16_t *statusArg) +{ + if (mProgressListener) + { + // We need to filter out non-error error codes. + // Is the only NS_SUCCEEDED value NS_OK? + switch ( status ) + { + case NS_NET_STATUS_RESOLVING_HOST: + case NS_NET_STATUS_RESOLVED_HOST: + case NS_NET_STATUS_BEGIN_FTP_TRANSACTION: + case NS_NET_STATUS_END_FTP_TRANSACTION: + case NS_NET_STATUS_CONNECTING_TO: + case NS_NET_STATUS_CONNECTED_TO: + case NS_NET_STATUS_SENDING_TO: + case NS_NET_STATUS_RECEIVING_FROM: + case NS_NET_STATUS_WAITING_FOR: + case NS_NET_STATUS_READING: + case NS_NET_STATUS_WRITING: + break; + + default: + // Pass other notifications (for legitimate errors) along. + mProgressListener->OnStatusChange(nullptr, request, status, statusArg); + break; + } + + } + + // If our progress listener implements nsIProgressEventSink, + // forward the notification + if (mEventSink) + { + mEventSink->OnStatus(request, ctxt, status, statusArg); + } + + return NS_OK; +} + + +//***************************************************************************** +// nsWebBrowserPersist private methods +//***************************************************************************** + +// Convert error info into proper message text and send OnStatusChange notification +// to the web progress listener. +nsresult nsWebBrowserPersist::SendErrorStatusChange( + bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + if (!mProgressListener) + { + // Do nothing + return NS_OK; + } + + // Get the file path or spec from the supplied URI + nsCOMPtr<nsIFile> file; + GetLocalFileFromURI(aURI, getter_AddRefs(file)); + nsAutoString path; + nsresult rv; + if (file) + { + file->GetPath(path); + } + else + { + nsAutoCString fileurl; + rv = aURI->GetSpec(fileurl); + NS_ENSURE_SUCCESS(rv, rv); + AppendUTF8toUTF16(fileurl, path); + } + + nsAutoString msgId; + switch(aResult) + { + case NS_ERROR_FILE_NAME_TOO_LONG: + // File name too long. + msgId.AssignLiteral("fileNameTooLongError"); + break; + case NS_ERROR_FILE_ALREADY_EXISTS: + // File exists with same name as directory. + msgId.AssignLiteral("fileAlreadyExistsError"); + break; + case NS_ERROR_FILE_DISK_FULL: + case NS_ERROR_FILE_NO_DEVICE_SPACE: + // Out of space on target volume. + msgId.AssignLiteral("diskFull"); + break; + + case NS_ERROR_FILE_READ_ONLY: + // Attempt to write to read/only file. + msgId.AssignLiteral("readOnly"); + break; + + case NS_ERROR_FILE_ACCESS_DENIED: + // Attempt to write without sufficient permissions. + msgId.AssignLiteral("accessError"); + break; + + default: + // Generic read/write error message. + if (aIsReadError) + msgId.AssignLiteral("readError"); + else + msgId.AssignLiteral("writeError"); + break; + } + // Get properties file bundle and extract status string. + nsCOMPtr<nsIStringBundleService> s = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> bundle; + rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE); + + nsXPIDLString msgText; + const char16_t *strings[1]; + strings[0] = path.get(); + rv = bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText); + + return NS_OK; +} + +nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const +{ + NS_ENSURE_ARG_POINTER(aObject); + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject); + if (objAsFile) + { + return NS_NewFileURI(aURI, objAsFile); + } + nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject); + if (objAsURI) + { + *aURI = objAsURI; + NS_ADDREF(*aURI); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +/* static */ nsresult +nsWebBrowserPersist::GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile) +{ + nsresult rv; + + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + file.forget(aLocalFile); + return NS_OK; +} + +/* static */ nsresult +nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString newPath; + nsresult rv = aURI->GetPath(newPath); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Append a forward slash if necessary + int32_t len = newPath.Length(); + if (len > 0 && newPath.CharAt(len - 1) != '/') + { + newPath.Append('/'); + } + + // Store the path back on the URI + AppendUTF16toUTF8(aPath, newPath); + aURI->SetPath(newPath); + + return NS_OK; +} + +nsresult nsWebBrowserPersist::SaveURIInternal( + nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, + uint32_t aReferrerPolicy, nsIInputStream *aPostData, + const char *aExtraHeaders, nsIURI *aFile, + bool aCalcFileExt, bool aIsPrivate) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv = NS_OK; + + mURI = aURI; + + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; + if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE) + { + loadFlags |= nsIRequest::LOAD_BYPASS_CACHE; + } + else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE) + { + loadFlags |= nsIRequest::LOAD_FROM_CACHE; + } + + // Extract the cache key + nsCOMPtr<nsISupports> cacheKey; + if (aCacheKey) + { + // Test if the cache key is actually a web page descriptor (docshell) + // or session history entry. + nsCOMPtr<nsISHEntry> shEntry = do_QueryInterface(aCacheKey); + if (!shEntry) + { + nsCOMPtr<nsIWebPageDescriptor> webPageDescriptor = + do_QueryInterface(aCacheKey); + if (webPageDescriptor) + { + nsCOMPtr<nsISupports> currentDescriptor; + webPageDescriptor->GetCurrentDescriptor(getter_AddRefs(currentDescriptor)); + shEntry = do_QueryInterface(currentDescriptor); + } + } + + if (shEntry) + { + shEntry->GetCacheKey(getter_AddRefs(cacheKey)); + } + else + { + // Assume a plain cache key + cacheKey = aCacheKey; + } + } + + // Open a channel to the URI + nsCOMPtr<nsIChannel> inputChannel; + rv = NS_NewChannel(getter_AddRefs(inputChannel), + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + static_cast<nsIInterfaceRequestor*>(this), + loadFlags); + + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(inputChannel); + if (pbChannel) + { + pbChannel->SetPrivate(aIsPrivate); + } + + if (NS_FAILED(rv) || inputChannel == nullptr) + { + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + // Disable content conversion + if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION) + { + nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(inputChannel)); + if (encodedChannel) + { + encodedChannel->SetApplyConversion(false); + } + } + + if (mPersistFlags & PERSIST_FLAGS_FORCE_ALLOW_COOKIES) + { + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(inputChannel); + if (httpChannelInternal) + httpChannelInternal->SetThirdPartyFlags(nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); + } + + // Set the referrer, post data and headers if any + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel)); + if (httpChannel) + { + // Referrer + if (aReferrer) + { + httpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy); + } + + // Post data + if (aPostData) + { + nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData)); + if (stream) + { + // Rewind the postdata stream + stream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); + // Attach the postdata to the http channel + uploadChannel->SetUploadStream(aPostData, EmptyCString(), -1); + } + } + + // Cache key + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel)); + if (cacheChannel && cacheKey) + { + cacheChannel->SetCacheKey(cacheKey); + } + + // Headers + if (aExtraHeaders) + { + nsAutoCString oneHeader; + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf = 0; + int32_t colon = 0; + const char *kWhitespace = "\b\t\r\n "; + nsAutoCString extraHeaders(aExtraHeaders); + while (true) + { + crlf = extraHeaders.Find("\r\n", true); + if (crlf == -1) + break; + extraHeaders.Mid(oneHeader, 0, crlf); + extraHeaders.Cut(0, crlf + 2); + colon = oneHeader.Find(":"); + if (colon == -1) + break; // Should have a colon + oneHeader.Left(headerName, colon); + colon++; + oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); + headerName.Trim(kWhitespace); + headerValue.Trim(kWhitespace); + // Add the header (merging if required) + rv = httpChannel->SetRequestHeader(headerName, headerValue, true); + if (NS_FAILED(rv)) + { + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + } + } + } + return SaveChannelInternal(inputChannel, aFile, aCalcFileExt); +} + +nsresult nsWebBrowserPersist::SaveChannelInternal( + nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt) +{ + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aFile); + + // The default behaviour of SaveChannelInternal is to download the source + // into a storage stream and upload that to the target. MakeOutputStream + // special-cases a file target and creates a file output stream directly. + // We want to special-case a file source and create a file input stream, + // but we don't need to do this in the case of a file target. + nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(aChannel)); + nsCOMPtr<nsIFileURL> fu(do_QueryInterface(aFile)); + + if (fc && !fu) { + nsCOMPtr<nsIInputStream> fileInputStream, bufferedInputStream; + nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel, + getter_AddRefs(fileInputStream)); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream), + fileInputStream, BUFFERED_OUTPUT_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString contentType; + aChannel->GetContentType(contentType); + return StartUpload(bufferedInputStream, aFile, contentType); + } + + // Read from the input channel + nsresult rv = NS_MaybeOpenChannelUsingAsyncOpen2(aChannel, this); + if (rv == NS_ERROR_NO_CONTENT) + { + // Assume this is a protocol such as mailto: which does not feed out + // data and just ignore it. + return NS_SUCCESS_DONT_FIXUP; + } + + if (NS_FAILED(rv)) + { + // Opening failed, but do we care? + if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS) + { + SendErrorStatusChange(true, rv, aChannel, aFile); + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + return NS_SUCCESS_DONT_FIXUP; + } + + // Add the output transport to the output map with the channel as the key + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel); + mOutputMap.Put(keyPtr, new OutputData(aFile, mURI, aCalcFileExt)); + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::GetExtensionForContentType(const char16_t *aContentType, char16_t **aExt) +{ + NS_ENSURE_ARG_POINTER(aContentType); + NS_ENSURE_ARG_POINTER(aExt); + + *aExt = nullptr; + + nsresult rv; + if (!mMIMEService) + { + mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); + } + + nsAutoCString contentType; + contentType.AssignWithConversion(aContentType); + nsAutoCString ext; + rv = mMIMEService->GetPrimaryExtension(contentType, EmptyCString(), ext); + if (NS_SUCCEEDED(rv)) + { + *aExt = UTF8ToNewUnicode(ext); + NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsWebBrowserPersist::SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData) +{ + nsresult rv = + SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath); + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, mURI); + EndDownload(rv); + } + return rv; +} + +nsresult nsWebBrowserPersist::SaveDocumentInternal( + nsIWebBrowserPersistDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath) +{ + mURI = nullptr; + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv = aDocument->SetPersistFlags(mPersistFlags); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDocument->GetIsPrivate(&mIsPrivate); + NS_ENSURE_SUCCESS(rv, rv); + + // See if we can get the local file representation of this URI + nsCOMPtr<nsIFile> localFile; + rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile)); + + nsCOMPtr<nsIFile> localDataPath; + if (NS_SUCCEEDED(rv) && aDataPath) + { + // See if we can get the local file representation of this URI + rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + // Persist the main document + rv = aDocument->GetCharacterSet(mCurrentCharset); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString uriSpec; + rv = aDocument->GetDocumentURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(mURI), uriSpec, mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aDocument->GetBaseURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(mCurrentBaseURI), uriSpec, + mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + + // Does the caller want to fixup the referenced URIs and save those too? + if (aDataPath) + { + // Basic steps are these. + // + // 1. Iterate through the document (and subdocuments) building a list + // of unique URIs. + // 2. For each URI create an OutputData entry and open a channel to save + // it. As each URI is saved, discover the mime type and fix up the + // local filename with the correct extension. + // 3. Store the document in a list and wait for URI persistence to finish + // 4. After URI persistence completes save the list of documents, + // fixing it up as it goes out to file. + + mCurrentDataPathIsRelative = false; + mCurrentDataPath = aDataPath; + mCurrentRelativePathToData = ""; + mCurrentThingsToPersist = 0; + mTargetBaseURI = aFile; + + // Determine if the specified data path is relative to the + // specified file, (e.g. c:\docs\htmldata is relative to + // c:\docs\myfile.htm, but not to d:\foo\data. + + // Starting with the data dir work back through its parents + // checking if one of them matches the base directory. + + if (localDataPath && localFile) + { + nsCOMPtr<nsIFile> baseDir; + localFile->GetParent(getter_AddRefs(baseDir)); + + nsAutoCString relativePathToData; + nsCOMPtr<nsIFile> dataDirParent; + dataDirParent = localDataPath; + while (dataDirParent) + { + bool sameDir = false; + dataDirParent->Equals(baseDir, &sameDir); + if (sameDir) + { + mCurrentRelativePathToData = relativePathToData; + mCurrentDataPathIsRelative = true; + break; + } + + nsAutoString dirName; + dataDirParent->GetLeafName(dirName); + + nsAutoCString newRelativePathToData; + newRelativePathToData = NS_ConvertUTF16toUTF8(dirName) + + NS_LITERAL_CSTRING("/") + + relativePathToData; + relativePathToData = newRelativePathToData; + + nsCOMPtr<nsIFile> newDataDirParent; + rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent)); + dataDirParent = newDataDirParent; + } + } + else + { + // generate a relative path if possible + nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile)); + if (pathToBaseURL) + { + nsAutoCString relativePath; // nsACString + if (NS_SUCCEEDED(pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) + { + mCurrentDataPathIsRelative = true; + mCurrentRelativePathToData = relativePath; + } + } + } + + // Store the document in a list so when URI persistence is done and the + // filenames of saved URIs are known, the documents can be fixed up and + // saved + + DocData *docData = new DocData; + docData->mBaseURI = mCurrentBaseURI; + docData->mCharset = mCurrentCharset; + docData->mDocument = aDocument; + docData->mFile = aFile; + mDocList.AppendElement(docData); + + // Walk the DOM gathering a list of externally referenced URIs in the uri map + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit = + new OnWalk(this, aFile, localDataPath); + return aDocument->ReadResources(visit); + } + else + { + DocData *docData = new DocData; + docData->mBaseURI = mCurrentBaseURI; + docData->mCharset = mCurrentCharset; + docData->mDocument = aDocument; + docData->mFile = aFile; + mDocList.AppendElement(docData); + + // Not walking DOMs, so go directly to serialization. + SerializeNextFile(); + return NS_OK; + } +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::VisitResource(nsIWebBrowserPersistDocument* aDoc, + const nsACString& aURI) +{ + return mParent->StoreURI(nsAutoCString(aURI).get()); +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::VisitDocument(nsIWebBrowserPersistDocument* aDoc, + nsIWebBrowserPersistDocument* aSubDoc) +{ + URIData* data = nullptr; + nsAutoCString uriSpec; + nsresult rv = aSubDoc->GetDocumentURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = mParent->StoreURI(uriSpec.get(), false, &data); + NS_ENSURE_SUCCESS(rv, rv); + if (!data) { + // If the URI scheme isn't persistable, then don't persist. + return NS_OK; + } + data->mIsSubFrame = true; + return mParent->SaveSubframeContent(aSubDoc, uriSpec, data); +} + + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc, + nsresult aStatus) +{ + if (NS_FAILED(aStatus)) { + mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile); + mParent->EndDownload(aStatus); + return aStatus; + } + mParent->FinishSaveDocumentInternal(mFile, mDataPath); + return NS_OK; +} + +void +nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile, + nsIFile* aDataPath) +{ + // If there are things to persist, create a directory to hold them + if (mCurrentThingsToPersist > 0) { + if (aDataPath) { + bool exists = false; + bool haveDir = false; + + aDataPath->Exists(&exists); + if (exists) { + aDataPath->IsDirectory(&haveDir); + } + if (!haveDir) { + nsresult rv = + aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_SUCCEEDED(rv)) { + haveDir = true; + } else { + SendErrorStatusChange(false, rv, nullptr, aFile); + } + } + if (!haveDir) { + EndDownload(NS_ERROR_FAILURE); + return; + } + if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) { + // Add to list of things to delete later if all goes wrong + CleanupData *cleanupData = new CleanupData; + cleanupData->mFile = aDataPath; + cleanupData->mIsDirectory = true; + mCleanupList.AppendElement(cleanupData); + } + } + } + + if (mWalkStack.Length() > 0) { + mozilla::UniquePtr<WalkData> toWalk; + mWalkStack.LastElement().swap(toWalk); + mWalkStack.TruncateLength(mWalkStack.Length() - 1); + // Bounce this off the event loop to avoid stack overflow. + typedef StoreCopyPassByRRef<decltype(toWalk)> WalkStorage; + auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred; + nsCOMPtr<nsIRunnable> saveLater = + NewRunnableMethod<WalkStorage>(this, saveMethod, + mozilla::Move(toWalk)); + NS_DispatchToCurrentThread(saveLater); + } else { + // Done walking DOMs; on to the serialization phase. + SerializeNextFile(); + } +} + +void nsWebBrowserPersist::Cleanup() +{ + mURIMap.Clear(); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } + mOutputMap.Clear(); + + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } + mUploadList.Clear(); + + uint32_t i; + for (i = 0; i < mDocList.Length(); i++) { + DocData *docData = mDocList.ElementAt(i); + delete docData; + } + mDocList.Clear(); + + for (i = 0; i < mCleanupList.Length(); i++) { + CleanupData *cleanupData = mCleanupList.ElementAt(i); + delete cleanupData; + } + mCleanupList.Clear(); + + mFilenameList.Clear(); +} + +void nsWebBrowserPersist::CleanupLocalFiles() +{ + // Two passes, the first pass cleans up files, the second pass tests + // for and then deletes empty directories. Directories that are not + // empty after the first pass must contain files from something else + // and are not deleted. + int pass; + for (pass = 0; pass < 2; pass++) + { + uint32_t i; + for (i = 0; i < mCleanupList.Length(); i++) + { + CleanupData *cleanupData = mCleanupList.ElementAt(i); + nsCOMPtr<nsIFile> file = cleanupData->mFile; + + // Test if the dir / file exists (something in an earlier loop + // may have already removed it) + bool exists = false; + file->Exists(&exists); + if (!exists) + continue; + + // Test if the file has changed in between creation and deletion + // in some way that means it should be ignored + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory != cleanupData->mIsDirectory) + continue; // A file has become a dir or vice versa ! + + if (pass == 0 && !isDirectory) + { + file->Remove(false); + } + else if (pass == 1 && isDirectory) // Directory + { + // Directories are more complicated. Enumerate through + // children looking for files. Any files created by the + // persist object would have been deleted by the first + // pass so if there are any there at this stage, the dir + // cannot be deleted because it has someone else's files + // in it. Empty child dirs are deleted but they must be + // recursed through to ensure they are actually empty. + + bool isEmptyDirectory = true; + nsCOMArray<nsISimpleEnumerator> dirStack; + int32_t stackSize = 0; + + // Push the top level enum onto the stack + nsCOMPtr<nsISimpleEnumerator> pos; + if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos)))) + dirStack.AppendObject(pos); + + while (isEmptyDirectory && (stackSize = dirStack.Count())) + { + // Pop the last element + nsCOMPtr<nsISimpleEnumerator> curPos; + curPos = dirStack[stackSize-1]; + dirStack.RemoveObjectAt(stackSize - 1); + + // Test if the enumerator has any more files in it + bool hasMoreElements = false; + curPos->HasMoreElements(&hasMoreElements); + if (!hasMoreElements) + { + continue; + } + + // Child files automatically make this code drop out, + // while child dirs keep the loop going. + nsCOMPtr<nsISupports> child; + curPos->GetNext(getter_AddRefs(child)); + NS_ASSERTION(child, "No child element, but hasMoreElements says otherwise"); + if (!child) + continue; + nsCOMPtr<nsIFile> childAsFile = do_QueryInterface(child); + NS_ASSERTION(childAsFile, "This should be a file but isn't"); + + bool childIsSymlink = false; + childAsFile->IsSymlink(&childIsSymlink); + bool childIsDir = false; + childAsFile->IsDirectory(&childIsDir); + if (!childIsDir || childIsSymlink) + { + // Some kind of file or symlink which means dir + // is not empty so just drop out. + isEmptyDirectory = false; + break; + } + // Push parent enumerator followed by child enumerator + nsCOMPtr<nsISimpleEnumerator> childPos; + childAsFile->GetDirectoryEntries(getter_AddRefs(childPos)); + dirStack.AppendObject(curPos); + if (childPos) + dirStack.AppendObject(childPos); + + } + dirStack.Clear(); + + // If after all that walking the dir is deemed empty, delete it + if (isEmptyDirectory) + { + file->Remove(true); + } + } + } + } +} + +nsresult +nsWebBrowserPersist::CalculateUniqueFilename(nsIURI *aURI) +{ + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); + + bool nameHasChanged = false; + nsresult rv; + + // Get the old filename + nsAutoCString filename; + rv = url->GetFileName(filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + nsAutoCString directory; + rv = url->GetDirectory(directory); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Split the filename into a base and an extension. + // e.g. "foo.html" becomes "foo" & ".html" + // + // The nsIURL methods GetFileBaseName & GetFileExtension don't + // preserve the dot whereas this code does to save some effort + // later when everything is put back together. + int32_t lastDot = filename.RFind("."); + nsAutoCString base; + nsAutoCString ext; + if (lastDot >= 0) + { + filename.Mid(base, 0, lastDot); + filename.Mid(ext, lastDot, filename.Length() - lastDot); // includes dot + } + else + { + // filename contains no dot + base = filename; + } + + // Test if the filename is longer than allowed by the OS + int32_t needToChop = filename.Length() - kDefaultMaxFilenameLength; + if (needToChop > 0) + { + // Truncate the base first and then the ext if necessary + if (base.Length() > (uint32_t) needToChop) + { + base.Truncate(base.Length() - needToChop); + } + else + { + needToChop -= base.Length() - 1; + base.Truncate(1); + if (ext.Length() > (uint32_t) needToChop) + { + ext.Truncate(ext.Length() - needToChop); + } + else + { + ext.Truncate(0); + } + // If kDefaultMaxFilenameLength were 1 we'd be in trouble here, + // but that won't happen because it will be set to a sensible + // value. + } + + filename.Assign(base); + filename.Append(ext); + nameHasChanged = true; + } + + // Ensure the filename is unique + // Create a filename if it's empty, or if the filename / datapath is + // already taken by another URI and create an alternate name. + + if (base.IsEmpty() || !mFilenameList.IsEmpty()) + { + nsAutoCString tmpPath; + nsAutoCString tmpBase; + uint32_t duplicateCounter = 1; + while (1) + { + // Make a file name, + // Foo become foo_001, foo_002, etc. + // Empty files become _001, _002 etc. + + if (base.IsEmpty() || duplicateCounter > 1) + { + char * tmp = PR_smprintf("_%03d", duplicateCounter); + NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY); + if (filename.Length() < kDefaultMaxFilenameLength - 4) + { + tmpBase = base; + } + else + { + base.Mid(tmpBase, 0, base.Length() - 4); + } + tmpBase.Append(tmp); + PR_smprintf_free(tmp); + } + else + { + tmpBase = base; + } + + tmpPath.Assign(directory); + tmpPath.Append(tmpBase); + tmpPath.Append(ext); + + // Test if the name is a duplicate + if (!mFilenameList.Contains(tmpPath)) + { + if (!base.Equals(tmpBase)) + { + filename.Assign(tmpBase); + filename.Append(ext); + nameHasChanged = true; + } + break; + } + duplicateCounter++; + } + } + + // Add name to list of those already used + nsAutoCString newFilepath(directory); + newFilepath.Append(filename); + mFilenameList.AppendElement(newFilepath); + + // Update the uri accordingly if the filename actually changed + if (nameHasChanged) + { + // Final sanity test + if (filename.Length() > kDefaultMaxFilenameLength) + { + NS_WARNING("Filename wasn't truncated less than the max file length - how can that be?"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + + if (localFile) + { + nsAutoString filenameAsUnichar; + filenameAsUnichar.AssignWithConversion(filename.get()); + localFile->SetLeafName(filenameAsUnichar); + + // Resync the URI with the file after the extension has been appended + nsresult rv; + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + fileURL->SetFile(localFile); // this should recalculate uri + } + else + { + url->SetFileName(filename); + } + } + + return NS_OK; +} + + +nsresult +nsWebBrowserPersist::MakeFilenameFromURI(nsIURI *aURI, nsString &aFilename) +{ + // Try to get filename from the URI. + nsAutoString fileName; + + // Get a suggested file name from the URL but strip it of characters + // likely to cause the name to be illegal. + + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + if (url) + { + nsAutoCString nameFromURL; + url->GetFileName(nameFromURL); + if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) + { + fileName.AssignWithConversion(NS_UnescapeURL(nameFromURL).BeginReading()); + aFilename = fileName; + return NS_OK; + } + if (!nameFromURL.IsEmpty()) + { + // Unescape the file name (GetFileName escapes it) + NS_UnescapeURL(nameFromURL); + uint32_t nameLength = 0; + const char *p = nameFromURL.get(); + for (;*p && *p != ';' && *p != '?' && *p != '#' && *p != '.' + ;p++) + { + if (nsCRT::IsAsciiAlpha(*p) || nsCRT::IsAsciiDigit(*p) + || *p == '.' || *p == '-' || *p == '_' || (*p == ' ')) + { + fileName.Append(char16_t(*p)); + if (++nameLength == kDefaultMaxFilenameLength) + { + // Note: + // There is no point going any further since it will be + // truncated in CalculateUniqueFilename anyway. + // More importantly, certain implementations of + // nsIFile (e.g. the Mac impl) might truncate + // names in undesirable ways, such as truncating from + // the middle, inserting ellipsis and so on. + break; + } + } + } + } + } + + // Empty filenames can confuse the local file object later + // when it attempts to set the leaf name in CalculateUniqueFilename + // for duplicates and ends up replacing the parent dir. To avoid + // the problem, all filenames are made at least one character long. + if (fileName.IsEmpty()) + { + fileName.Append(char16_t('a')); // 'a' is for arbitrary + } + + aFilename = fileName; + return NS_OK; +} + + +nsresult +nsWebBrowserPersist::CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, nsIURI *aOriginalURIWithExtension) +{ + nsresult rv; + + if (!mMIMEService) + { + mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); + } + + nsAutoCString contentType; + + // Get the content type from the channel + aChannel->GetContentType(contentType); + + // Get the content type from the MIME service + if (contentType.IsEmpty()) + { + nsCOMPtr<nsIURI> uri; + aChannel->GetOriginalURI(getter_AddRefs(uri)); + mMIMEService->GetTypeFromURI(uri, contentType); + } + + // Append the extension onto the file + if (!contentType.IsEmpty()) + { + nsCOMPtr<nsIMIMEInfo> mimeInfo; + mMIMEService->GetFromTypeAndExtension( + contentType, EmptyCString(), getter_AddRefs(mimeInfo)); + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + + if (mimeInfo) + { + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); + + nsAutoCString newFileName; + url->GetFileName(newFileName); + + // Test if the current extension is current for the mime type + bool hasExtension = false; + int32_t ext = newFileName.RFind("."); + if (ext != -1) + { + mimeInfo->ExtensionExists(Substring(newFileName, ext + 1), &hasExtension); + } + + // Append the mime file extension + nsAutoCString fileExt; + if (!hasExtension) + { + // Test if previous extension is acceptable + nsCOMPtr<nsIURL> oldurl(do_QueryInterface(aOriginalURIWithExtension)); + NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE); + oldurl->GetFileExtension(fileExt); + bool useOldExt = false; + if (!fileExt.IsEmpty()) + { + mimeInfo->ExtensionExists(fileExt, &useOldExt); + } + + // can't use old extension so use primary extension + if (!useOldExt) + { + mimeInfo->GetPrimaryExtension(fileExt); + } + + if (!fileExt.IsEmpty()) + { + uint32_t newLength = newFileName.Length() + fileExt.Length() + 1; + if (newLength > kDefaultMaxFilenameLength) + { + if (fileExt.Length() > kDefaultMaxFilenameLength/2) + fileExt.Truncate(kDefaultMaxFilenameLength/2); + + uint32_t diff = kDefaultMaxFilenameLength - 1 - + fileExt.Length(); + if (newFileName.Length() > diff) + newFileName.Truncate(diff); + } + newFileName.Append('.'); + newFileName.Append(fileExt); + } + + if (localFile) + { + localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName)); + + // Resync the URI with the file after the extension has been appended + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + fileURL->SetFile(localFile); // this should recalculate uri + } + else + { + url->SetFileName(newFileName); + } + } + + } + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::MakeOutputStream( + nsIURI *aURI, nsIOutputStream **aOutputStream) +{ + nsresult rv; + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + if (localFile) + rv = MakeOutputStreamFromFile(localFile, aOutputStream); + else + rv = MakeOutputStreamFromURI(aURI, aOutputStream); + + return rv; +} + +nsresult +nsWebBrowserPersist::MakeOutputStreamFromFile( + nsIFile *aFile, nsIOutputStream **aOutputStream) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIFileOutputStream> fileOutputStream = + do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // XXX brade: get the right flags here! + int32_t ioFlags = -1; + if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE) + ioFlags = PR_APPEND | PR_CREATE_FILE | PR_WRONLY; + rv = fileOutputStream->Init(aFile, ioFlags, -1, 0); + NS_ENSURE_SUCCESS(rv, rv); + + *aOutputStream = NS_BufferOutputStream(fileOutputStream, + BUFFERED_OUTPUT_SIZE).take(); + + if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) + { + // Add to cleanup list in event of failure + CleanupData *cleanupData = new CleanupData; + if (!cleanupData) { + NS_RELEASE(*aOutputStream); + return NS_ERROR_OUT_OF_MEMORY; + } + cleanupData->mFile = aFile; + cleanupData->mIsDirectory = false; + mCleanupList.AppendElement(cleanupData); + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::MakeOutputStreamFromURI( + nsIURI *aURI, nsIOutputStream **aOutputStream) +{ + uint32_t segsize = 8192; + uint32_t maxsize = uint32_t(-1); + nsCOMPtr<nsIStorageStream> storStream; + nsresult rv = NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream), NS_ERROR_FAILURE); + return NS_OK; +} + +void +nsWebBrowserPersist::FinishDownload() +{ + EndDownload(NS_OK); +} + +void +nsWebBrowserPersist::EndDownload(nsresult aResult) +{ + // Store the error code in the result if it is an error + if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) + { + mPersistResult = aResult; + } + + // mCompleted needs to be set before issuing the stop notification. + // (Bug 1224437) + mCompleted = true; + // State stop notification + if (mProgressListener) { + mProgressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP + | nsIWebProgressListener::STATE_IS_NETWORK, mPersistResult); + } + + // Do file cleanup if required + if (NS_FAILED(aResult) && (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) + { + CleanupLocalFiles(); + } + + // Cleanup the channels + Cleanup(); + + mProgressListener = nullptr; + mProgressListener2 = nullptr; + mEventSink = nullptr; +} + +nsresult +nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel) +{ + NS_ENSURE_ARG_POINTER(aNewChannel); + + // Iterate through existing open channels looking for one with a URI + // matching the one specified. + nsCOMPtr<nsIURI> originalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(originalURI)); + nsISupports* matchingKey = nullptr; + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Key(); + nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key); + nsCOMPtr<nsIURI> thisURI; + + thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); + + // Compare this channel's URI to the one passed in. + bool matchingURI = false; + thisURI->Equals(originalURI, &matchingURI); + if (matchingURI) { + matchingKey = key; + break; + } + } + + if (matchingKey) { + // If a match was found, remove the data entry with the old channel + // key and re-add it with the new channel key. + nsAutoPtr<OutputData> outputData; + mOutputMap.RemoveAndForget(matchingKey, outputData); + NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); + + // Store data again with new channel unless told to ignore redirects. + if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) { + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel); + mOutputMap.Put(keyPtr, outputData.forget()); + } + } + + return NS_OK; +} + +void +nsWebBrowserPersist::CalcTotalProgress() +{ + mTotalCurrentProgress = 0; + mTotalMaxProgress = 0; + + if (mOutputMap.Count() > 0) { + // Total up the progress of each output stream + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + // Only count toward total progress if destination file is local. + OutputData* data = iter.UserData(); + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile); + if (fileURL) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } + } + + if (mUploadList.Count() > 0) { + // Total up the progress of each upload + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + UploadData* data = iter.UserData(); + if (data) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } + } + + // XXX this code seems pretty bogus and pointless + if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) + { + // No output streams so we must be complete + mTotalCurrentProgress = 10000; + mTotalMaxProgress = 10000; + } +} + +nsresult +nsWebBrowserPersist::StoreURI( + const char *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), + nsDependentCString(aURI), + mCurrentCharset.get(), + mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + + return StoreURI(uri, aNeedsPersisting, aData); +} + +nsresult +nsWebBrowserPersist::StoreURI( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + if (aData) + { + *aData = nullptr; + } + + // Test if this URI should be persisted. By default + // we should assume the URI is persistable. + bool doNotPersistURI; + nsresult rv = NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_NON_PERSISTABLE, + &doNotPersistURI); + if (NS_FAILED(rv)) + { + doNotPersistURI = false; + } + + if (doNotPersistURI) + { + return NS_OK; + } + + URIData *data = nullptr; + MakeAndStoreLocalFilenameInURIMap(aURI, aNeedsPersisting, &data); + if (aData) + { + *aData = data; + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::URIData::GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut) +{ + aSpecOut.SetIsVoid(true); + if (!mNeedsFixup) { + return NS_OK; + } + nsresult rv; + nsCOMPtr<nsIURI> fileAsURI; + if (mFile) { + rv = mFile->Clone(getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = mDataPath->Clone(getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = AppendPathToURI(fileAsURI, mFilename); + NS_ENSURE_SUCCESS(rv, rv); + } + + // remove username/password if present + fileAsURI->SetUserPass(EmptyCString()); + + // reset node attribute + // Use relative or absolute links + if (mDataPathIsRelative) { + bool isEqual = false; + if (NS_SUCCEEDED(mRelativeDocumentURI->Equals(targetBaseURI, &isEqual)) && isEqual) { + nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI)); + if (!url) { + return NS_ERROR_FAILURE; + } + + nsAutoCString filename; + url->GetFileName(filename); + + nsAutoCString rawPathURL(mRelativePathToData); + rawPathURL.Append(filename); + + rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsAutoCString rawPathURL; + + nsCOMPtr<nsIFile> dataFile; + rv = GetLocalFileFromURI(mFile, getter_AddRefs(dataFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> docFile; + rv = GetLocalFileFromURI(targetBaseURI, getter_AddRefs(docFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> parentDir; + rv = docFile->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dataFile->GetRelativePath(parentDir, rawPathURL); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + fileAsURI->GetSpec(aSpecOut); + } + if (mIsSubFrame) { + AppendUTF16toUTF8(mSubFrameExt, aSpecOut); + } + + return NS_OK; +} + +bool +nsWebBrowserPersist::DocumentEncoderExists(const char *aContentType) +{ + // Check if there is an encoder for the desired content type. + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aContentType); + + nsCOMPtr<nsIComponentRegistrar> registrar; + NS_GetComponentRegistrar(getter_AddRefs(registrar)); + if (registrar) + { + bool result; + nsresult rv = registrar->IsContractIDRegistered(contractID.get(), + &result); + if (NS_SUCCEEDED(rv) && result) + { + return true; + } + } + return false; +} + +nsresult +nsWebBrowserPersist::SaveSubframeContent( + nsIWebBrowserPersistDocument *aFrameContent, + const nsCString& aURISpec, + URIData *aData) +{ + NS_ENSURE_ARG_POINTER(aData); + + // Extract the content type for the frame's contents. + nsAutoCString contentType; + nsresult rv = aFrameContent->GetContentType(contentType); + NS_ENSURE_SUCCESS(rv, rv); + + nsXPIDLString ext; + GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(), + getter_Copies(ext)); + + // We must always have an extension so we will try to re-assign + // the original extension if GetExtensionForContentType fails. + if (ext.IsEmpty()) { + nsCOMPtr<nsIURI> docURI; + rv = NS_NewURI(getter_AddRefs(docURI), aURISpec, mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv)); + nsAutoCString extension; + if (NS_SUCCEEDED(rv)) { + url->GetFileExtension(extension); + } else { + extension.AssignLiteral("htm"); + } + aData->mSubFrameExt.Assign(char16_t('.')); + AppendUTF8toUTF16(extension, aData->mSubFrameExt); + } else { + aData->mSubFrameExt.Assign(char16_t('.')); + aData->mSubFrameExt.Append(ext); + } + + nsString filenameWithExt = aData->mFilename; + filenameWithExt.Append(aData->mSubFrameExt); + + // Work out the path for the subframe + nsCOMPtr<nsIURI> frameURI; + rv = mCurrentDataPath->Clone(getter_AddRefs(frameURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = AppendPathToURI(frameURI, filenameWithExt); + NS_ENSURE_SUCCESS(rv, rv); + + // Work out the path for the subframe data + nsCOMPtr<nsIURI> frameDataURI; + rv = mCurrentDataPath->Clone(getter_AddRefs(frameDataURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString newFrameDataPath(aData->mFilename); + + // Append _data + newFrameDataPath.AppendLiteral("_data"); + rv = AppendPathToURI(frameDataURI, newFrameDataPath); + NS_ENSURE_SUCCESS(rv, rv); + + // Make frame document & data path conformant and unique + rv = CalculateUniqueFilename(frameURI); + NS_ENSURE_SUCCESS(rv, rv); + rv = CalculateUniqueFilename(frameDataURI); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentThingsToPersist++; + + // We shouldn't use SaveDocumentInternal for the contents + // of frames that are not documents, e.g. images. + if (DocumentEncoderExists(contentType.get())) { + auto toWalk = mozilla::MakeUnique<WalkData>(); + toWalk->mDocument = aFrameContent; + toWalk->mFile = frameURI; + toWalk->mDataPath = frameDataURI; + mWalkStack.AppendElement(mozilla::Move(toWalk)); + } else { + rv = StoreURI(aURISpec.get()); + } + NS_ENSURE_SUCCESS(rv, rv); + + // Store the updated uri to the frame + aData->mFile = frameURI; + aData->mSubFrameExt.Truncate(); // we already put this in frameURI + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel) +{ + nsresult rv = NS_OK; + *aChannel = nullptr; + + rv = NS_NewChannel(aChannel, + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(*aChannel); + + rv = (*aChannel)->SetNotificationCallbacks(static_cast<nsIInterfaceRequestor*>(this)); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// we store the current location as the key (absolutized version of domnode's attribute's value) +nsresult +nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString spec; + nsresult rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Create a sensibly named filename for the URI and store in the URI map + URIData *data; + if (mURIMap.Contains(spec)) + { + data = mURIMap.Get(spec); + if (aNeedsPersisting) + { + data->mNeedsPersisting = true; + } + if (aData) + { + *aData = data; + } + return NS_OK; + } + + // Create a unique file name for the uri + nsString filename; + rv = MakeFilenameFromURI(aURI, filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Store the file name + data = new URIData; + NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY); + + data->mNeedsPersisting = aNeedsPersisting; + data->mNeedsFixup = true; + data->mFilename = filename; + data->mSaved = false; + data->mIsSubFrame = false; + data->mDataPath = mCurrentDataPath; + data->mDataPathIsRelative = mCurrentDataPathIsRelative; + data->mRelativePathToData = mCurrentRelativePathToData; + data->mRelativeDocumentURI = mTargetBaseURI; + data->mCharset = mCurrentCharset; + + if (aNeedsPersisting) + mCurrentThingsToPersist++; + + mURIMap.Put(spec, data); + if (aData) + { + *aData = data; + } + + return NS_OK; +} + +// Decide if we need to apply conversion to the passed channel. +void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel *aChannel) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aChannel, &rv); + if (NS_FAILED(rv)) + return; + + // Set the default conversion preference: + encChannel->SetApplyConversion(false); + + nsCOMPtr<nsIURI> thisURI; + aChannel->GetURI(getter_AddRefs(thisURI)); + nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(thisURI)); + if (!sourceURL) + return; + nsAutoCString extension; + sourceURL->GetFileExtension(extension); + + nsCOMPtr<nsIUTF8StringEnumerator> encEnum; + encChannel->GetContentEncodings(getter_AddRefs(encEnum)); + if (!encEnum) + return; + nsCOMPtr<nsIExternalHelperAppService> helperAppService = + do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return; + bool hasMore; + rv = encEnum->HasMore(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + { + nsAutoCString encType; + rv = encEnum->GetNext(encType); + if (NS_SUCCEEDED(rv)) + { + bool applyConversion = false; + rv = helperAppService->ApplyDecodingForExtension(extension, encType, + &applyConversion); + if (NS_SUCCEEDED(rv)) + encChannel->SetApplyConversion(applyConversion); + } + } +} diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h new file mode 100644 index 000000000..1816af0a6 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +#ifndef nsWebBrowserPersist_h__ +#define nsWebBrowserPersist_h__ + +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +#include "nsIInterfaceRequestor.h" +#include "nsIMIMEService.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIChannel.h" +#include "nsIDocumentEncoder.h" +#include "nsITransport.h" +#include "nsIProgressEventSink.h" +#include "nsIFile.h" +#include "nsIWebProgressListener2.h" +#include "nsIWebBrowserPersistDocument.h" + +#include "mozilla/UniquePtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" + +#include "nsCWebBrowserPersist.h" + +class nsIStorageStream; +class nsIWebBrowserPersistDocument; + +class nsWebBrowserPersist final : public nsIInterfaceRequestor, + public nsIWebBrowserPersist, + public nsIStreamListener, + public nsIProgressEventSink, + public nsSupportsWeakReference +{ + friend class nsEncoderNodeFixup; + +// Public members +public: + nsWebBrowserPersist(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICANCELABLE + NS_DECL_NSIWEBBROWSERPERSIST + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPROGRESSEVENTSINK + +// Private members +private: + virtual ~nsWebBrowserPersist(); + nsresult SaveURIInternal( + nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, + uint32_t aReferrerPolicy, nsIInputStream *aPostData, + const char *aExtraHeaders, nsIURI *aFile, + bool aCalcFileExt, bool aIsPrivate); + nsresult SaveChannelInternal( + nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt); + nsresult SaveDocumentInternal( + nsIWebBrowserPersistDocument *aDocument, + nsIURI *aFile, + nsIURI *aDataPath); + nsresult SaveDocuments(); + void FinishSaveDocumentInternal(nsIURI* aFile, nsIFile* aDataPath); + nsresult GetExtensionForContentType( + const char16_t *aContentType, char16_t **aExt); + + struct CleanupData; + struct DocData; + struct OutputData; + struct UploadData; + struct URIData; + struct WalkData; + struct URIFixupData; + + class OnWalk; + class OnWrite; + class FlatURIMap; + friend class OnWalk; + friend class OnWrite; + + nsresult SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData); + void Cleanup(); + void CleanupLocalFiles(); + nsresult GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const; + static nsresult GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile); + static nsresult AppendPathToURI(nsIURI *aURI, const nsAString & aPath); + nsresult MakeAndStoreLocalFilenameInURIMap( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData); + nsresult MakeOutputStream( + nsIURI *aFile, nsIOutputStream **aOutputStream); + nsresult MakeOutputStreamFromFile( + nsIFile *aFile, nsIOutputStream **aOutputStream); + nsresult MakeOutputStreamFromURI(nsIURI *aURI, nsIOutputStream **aOutStream); + nsresult CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel); + nsresult StartUpload(nsIStorageStream *aOutStream, nsIURI *aDestinationURI, + const nsACString &aContentType); + nsresult StartUpload(nsIInputStream *aInputStream, nsIURI *aDestinationURI, + const nsACString &aContentType); + nsresult CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, + nsIURI *aOriginalURIWithExtension); + nsresult CalculateUniqueFilename(nsIURI *aURI); + nsresult MakeFilenameFromURI( + nsIURI *aURI, nsString &aFilename); + nsresult StoreURI( + const char *aURI, + bool aNeedsPersisting = true, + URIData **aData = nullptr); + nsresult StoreURI( + nsIURI *aURI, + bool aNeedsPersisting = true, + URIData **aData = nullptr); + bool DocumentEncoderExists(const char *aContentType); + + nsresult SaveSubframeContent( + nsIWebBrowserPersistDocument *aFrameContent, + const nsCString& aURISpec, + URIData *aData); + nsresult SendErrorStatusChange( + bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI); + + nsresult FixRedirectedChannelEntry(nsIChannel *aNewChannel); + + void EndDownload(nsresult aResult); + void FinishDownload(); + void SerializeNextFile(); + void CalcTotalProgress(); + + void SetApplyConversionIfNeeded(nsIChannel *aChannel); + + nsCOMPtr<nsIURI> mCurrentDataPath; + bool mCurrentDataPathIsRelative; + nsCString mCurrentRelativePathToData; + nsCOMPtr<nsIURI> mCurrentBaseURI; + nsCString mCurrentCharset; + nsCOMPtr<nsIURI> mTargetBaseURI; + uint32_t mCurrentThingsToPersist; + + nsCOMPtr<nsIMIMEService> mMIMEService; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIWebProgressListener> mProgressListener; + /** + * Progress listener for 64-bit values; this is the same object as + * mProgressListener, but is a member to avoid having to qi it for each + * progress notification. + */ + nsCOMPtr<nsIWebProgressListener2> mProgressListener2; + nsCOMPtr<nsIProgressEventSink> mEventSink; + nsClassHashtable<nsISupportsHashKey, OutputData> mOutputMap; + nsClassHashtable<nsISupportsHashKey, UploadData> mUploadList; + nsClassHashtable<nsCStringHashKey, URIData> mURIMap; + nsCOMPtr<nsIWebBrowserPersistURIMap> mFlatURIMap; + nsTArray<mozilla::UniquePtr<WalkData>> mWalkStack; + nsTArray<DocData*> mDocList; + nsTArray<CleanupData*> mCleanupList; + nsTArray<nsCString> mFilenameList; + bool mFirstAndOnlyUse; + bool mSavingDocument; + bool mCancel; + bool mCompleted; + bool mStartSaving; + bool mReplaceExisting; + bool mSerializingOutput; + bool mIsPrivate; + uint32_t mPersistFlags; + nsresult mPersistResult; + int64_t mTotalCurrentProgress; + int64_t mTotalMaxProgress; + int16_t mWrapColumn; + uint32_t mEncodingFlags; + nsString mContentType; +}; + +#endif |