The biggest set of APIs from ns[T]StringObsolete which are still heavily used are the string searching APIs. It appears the intention was for these to be replaced by the `FindInReadable` APIs, however that doesn't appear to have happened. In addition, the APIs have some quirks around their handling of mixed character widths. These APIs generally supported both narrow strings and the native string type, probably because char16_t string literals weren't available until c++11. Finally they also used easy-to-confuse unlabeled boolean and integer optional arguments to control behaviour. These patches do the following major changes to the searching APIs: 1. The ASCII case-insensitive search method was split out as LowerCaseFindASCII, rather than using a boolean. This should be less error-prone and more explicit, and allows the method to continue to use narrow string literals for all string types (as only ASCII is supported). 2. The other [R]Find methods were restricted to only support arguments with matching character types. I considered adding a FindASCII method which would use narrow string literals for both wide and narrow strings but it would've been the same amount of work as changing all of the literals to unicode literals. This ends up being the bulk of the changes in the patch. 3. All find methods were re-implemented using std::basic_string_view's find algorithm or stl algorithms to reduce code complexity, and avoid the need to carry around the logic from nsStringObsolete.cpp. 4. The implementations were moved to nsTStringRepr.cpp. 5. An overload of Find was added to try to catch callers which previously called `Find(..., false)` or `Find(..., true)` to set case-sensitivity, due to booleans normally implicitly coercing to `index_type`. This should probably be removed at some point, but may be useful during the transition. Differential Revision: https://phabricator.services.mozilla.com/D148300
2730 lines
90 KiB
C++
2730 lines
90 KiB
C++
/* -*- Mode: C++; 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 "mozilla/ArrayUtils.h"
|
|
#include "mozilla/TextUtils.h"
|
|
|
|
#include "nspr.h"
|
|
|
|
#include "nsIFileStreams.h" // New Necko file streams
|
|
#include <algorithm>
|
|
|
|
#include "nsNetCID.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsIClassOfService.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsIPrivateBrowsingChannel.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsIStorageStream.h"
|
|
#include "nsISeekableStream.h"
|
|
#include "nsIHttpChannel.h"
|
|
#include "nsIEncodedChannel.h"
|
|
#include "nsIUploadChannel.h"
|
|
#include "nsICacheInfoChannel.h"
|
|
#include "nsIFileChannel.h"
|
|
#include "nsEscape.h"
|
|
#include "nsUnicharUtils.h"
|
|
#include "nsIStringEnumerator.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 "nsIFormControl.h"
|
|
#include "nsIThreadRetargetableRequest.h"
|
|
#include "nsContentUtils.h"
|
|
|
|
#include "nsIStringBundle.h"
|
|
#include "nsIProtocolHandler.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/net/CookieJarSettings.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/Printf.h"
|
|
#include "ReferrerInfo.h"
|
|
#include "nsIURIMutator.h"
|
|
#include "mozilla/WebBrowserPersistDocumentParent.h"
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
#include "mozilla/dom/WindowGlobalParent.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/PContentParent.h"
|
|
#include "mozilla/dom/BrowserParent.h"
|
|
#include "nsIDocumentEncoder.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;
|
|
nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
|
|
nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
|
|
nsContentPolicyType mContentPolicyType;
|
|
nsCString mRelativePathToData;
|
|
nsCString mCharset;
|
|
|
|
nsresult GetLocalURI(nsIURI* targetBaseURI, nsCString& aSpecOut);
|
|
};
|
|
|
|
// Information about the output stream
|
|
// Note that this data structure (and the map that nsWebBrowserPersist keeps,
|
|
// where these are values) is used from two threads: the main thread,
|
|
// and the background task thread.
|
|
// The background thread only writes to mStream (from OnDataAvailable), and
|
|
// this access is guarded using mStreamMutex. It reads the mFile member, which
|
|
// is only written to on the main thread when the object is constructed and
|
|
// from OnStartRequest (if mCalcFileExt), both guaranteed to happen before
|
|
// OnDataAvailable is fired.
|
|
// The main thread gets OnStartRequest, OnStopRequest, and progress sink events,
|
|
// and accesses the other members.
|
|
struct nsWebBrowserPersist::OutputData {
|
|
nsCOMPtr<nsIURI> mFile;
|
|
nsCOMPtr<nsIURI> mOriginalLocation;
|
|
nsCOMPtr<nsIOutputStream> mStream;
|
|
Mutex mStreamMutex MOZ_UNANNOTATED;
|
|
int64_t mSelfProgress;
|
|
int64_t mSelfProgressMax;
|
|
bool mCalcFileExt;
|
|
|
|
OutputData(nsIURI* aFile, nsIURI* aOriginalLocation, bool aCalcFileExt)
|
|
: mFile(aFile),
|
|
mOriginalLocation(aOriginalLocation),
|
|
mStreamMutex("nsWebBrowserPersist::OutputData::mStreamMutex"),
|
|
mSelfProgress(0),
|
|
mSelfProgressMax(10000),
|
|
mCalcFileExt(aCalcFileExt) {}
|
|
~OutputData() {
|
|
// Gaining this lock in the destructor is pretty icky. It should be OK
|
|
// because the only other place we lock the mutex is in OnDataAvailable,
|
|
// which will never itself cause the OutputData instance to be
|
|
// destroyed.
|
|
MutexAutoLock lock(mStreamMutex);
|
|
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),
|
|
mPendingDocuments(1),
|
|
mStatus(NS_OK) {}
|
|
|
|
NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
|
|
NS_DECL_ISUPPORTS
|
|
private:
|
|
RefPtr<nsWebBrowserPersist> mParent;
|
|
nsCOMPtr<nsIURI> mFile;
|
|
nsCOMPtr<nsIFile> mDataPath;
|
|
|
|
uint32_t mPendingDocuments;
|
|
nsresult mStatus;
|
|
|
|
virtual ~OnWalk() = default;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
|
|
nsIWebBrowserPersistResourceVisitor)
|
|
|
|
class nsWebBrowserPersist::OnRemoteWalk final
|
|
: public nsIWebBrowserPersistDocumentReceiver {
|
|
public:
|
|
OnRemoteWalk(nsIWebBrowserPersistResourceVisitor* aVisitor,
|
|
nsIWebBrowserPersistDocument* aDocument)
|
|
: mVisitor(aVisitor), mDocument(aDocument) {}
|
|
|
|
NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
|
|
NS_DECL_ISUPPORTS
|
|
private:
|
|
nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
|
|
nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
|
|
|
|
virtual ~OnRemoteWalk() = default;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnRemoteWalk,
|
|
nsIWebBrowserPersistDocumentReceiver)
|
|
|
|
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() = default;
|
|
};
|
|
|
|
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() = default;
|
|
};
|
|
|
|
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),
|
|
mOutputMapMutex("nsWebBrowserPersist::mOutputMapMutex"),
|
|
mFirstAndOnlyUse(true),
|
|
mSavingDocument(false),
|
|
mCancel(false),
|
|
mEndCalled(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(nsIThreadRetargetableStreamListener)
|
|
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);
|
|
mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT);
|
|
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, nsIPrincipal* aPrincipal, uint32_t aCacheKey,
|
|
nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
|
|
nsIInputStream* aPostData, const char* aExtraHeaders, nsISupports* aFile,
|
|
nsContentPolicyType aContentPolicyType, nsILoadContext* aPrivacyContext) {
|
|
bool isPrivate = aPrivacyContext && aPrivacyContext->UsePrivateBrowsing();
|
|
return SavePrivacyAwareURI(aURI, aPrincipal, aCacheKey, aReferrerInfo,
|
|
aCookieJarSettings, aPostData, aExtraHeaders,
|
|
aFile, aContentPolicyType, isPrivate);
|
|
}
|
|
|
|
NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI(
|
|
nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aCacheKey,
|
|
nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
|
|
nsIInputStream* aPostData, const char* aExtraHeaders, nsISupports* aFile,
|
|
nsContentPolicyType aContentPolicy, 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, aPrincipal, aContentPolicy, aCacheKey,
|
|
aReferrerInfo, aCookieJarSettings, 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<Document> localDoc = do_QueryInterface(aDocument);
|
|
if (localDoc) {
|
|
doc = new mozilla::WebBrowserPersistLocalDocument(localDoc);
|
|
} else {
|
|
rv = NS_ERROR_NO_INTERFACE;
|
|
}
|
|
}
|
|
|
|
bool closed = false;
|
|
if (doc && NS_SUCCEEDED(doc->GetIsClosed(&closed)) && !closed) {
|
|
rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI);
|
|
}
|
|
|
|
if (NS_FAILED(rv) || closed) {
|
|
SendErrorStatusChange(true, rv, nullptr, mURI);
|
|
EndDownload(rv);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) {
|
|
// No point cancelling if we're already complete.
|
|
if (mEndCalled) {
|
|
return NS_OK;
|
|
}
|
|
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->AsyncOpen(this);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
|
|
|
// add this to the upload list
|
|
nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
|
|
mUploadList.InsertOrUpdate(keyPtr, MakeUnique<UploadData>(aDestinationURI));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsWebBrowserPersist::SerializeNextFile() {
|
|
nsresult rv = NS_OK;
|
|
MOZ_ASSERT(mWalkStack.Length() == 0);
|
|
|
|
// First, handle gathered URIs.
|
|
// 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, and we can skip iterating mURIMap if there are none.
|
|
|
|
// Persist each file in the uri map. The document(s)
|
|
// will be saved after the last one of these is saved.
|
|
for (const auto& entry : mURIMap) {
|
|
URIData* data = entry.GetWeak();
|
|
|
|
if (!data->mNeedsPersisting || data->mSaved) {
|
|
continue;
|
|
}
|
|
|
|
// Create a URI from the key.
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = NS_NewURI(getter_AddRefs(uri), entry.GetKey(), data->mCharset.get());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
|
|
// Make a URI to save the data to.
|
|
nsCOMPtr<nsIURI> fileAsURI = data->mDataPath;
|
|
rv = AppendPathToURI(fileAsURI, data->mFilename, fileAsURI);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
|
|
rv = SaveURIInternal(uri, data->mTriggeringPrincipal,
|
|
data->mContentPolicyType, 0, nullptr,
|
|
data->mCookieJarSettings, 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) {
|
|
// URIData.mFile will be updated to point to the correct
|
|
// URI object when it is fixed up with the right file extension
|
|
// in OnStartRequest
|
|
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("nsWebBrowserPersist::FinishDownload", 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 (const auto& uriEntry : mURIMap) {
|
|
nsAutoCString mapTo;
|
|
nsresult rv = uriEntry.GetWeak()->GetLocalURI(mTargetBaseURI, mapTo);
|
|
if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
|
|
flatMap->Add(uriEntry.GetKey(), mapTo);
|
|
}
|
|
}
|
|
mFlatURIMap = std::move(flatMap);
|
|
|
|
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("nsWebBrowserPersist::SerializeNextFile", mParent,
|
|
&nsWebBrowserPersist::SerializeNextFile));
|
|
return NS_OK;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsWebBrowserPersist::nsIRequestObserver
|
|
//*****************************************************************************
|
|
|
|
NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest(nsIRequest* request) {
|
|
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) {
|
|
nsCOMPtr<nsIThreadRetargetableRequest> r = do_QueryInterface(request);
|
|
// Determine if we're uploading. Only use OMT onDataAvailable if not.
|
|
nsCOMPtr<nsIFile> localFile;
|
|
GetLocalFileFromURI(data->mFile, getter_AddRefs(localFile));
|
|
if (r && localFile) {
|
|
if (!mBackgroundQueue) {
|
|
NS_CreateBackgroundTaskQueue("WebBrowserPersist",
|
|
getter_AddRefs(mBackgroundQueue));
|
|
}
|
|
if (mBackgroundQueue) {
|
|
r->RetargetDeliveryTo(mBackgroundQueue);
|
|
}
|
|
}
|
|
|
|
// 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)) {
|
|
nsCOMPtr<nsIURI> uriWithExt;
|
|
// this is the first point at which the server can tell us the mimetype
|
|
nsresult rv = CalculateAndAppendFileExt(
|
|
data->mFile, channel, data->mOriginalLocation, uriWithExt);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
data->mFile = uriWithExt;
|
|
}
|
|
|
|
// now make filename conformant and unique
|
|
nsCOMPtr<nsIURI> uniqueFilenameURI;
|
|
rv = CalculateUniqueFilename(data->mFile, uniqueFilenameURI);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
data->mFile = uniqueFilenameURI;
|
|
}
|
|
|
|
// The URIData entry is pointing to the old unfixed URI, so we need
|
|
// to update it.
|
|
nsCOMPtr<nsIURI> chanURI;
|
|
rv = channel->GetOriginalURI(getter_AddRefs(chanURI));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsAutoCString spec;
|
|
chanURI->GetSpec(spec);
|
|
URIData* uridata;
|
|
if (mURIMap.Get(spec, &uridata)) {
|
|
uridata->mFile = 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) {
|
|
{
|
|
MutexAutoLock lock(mOutputMapMutex);
|
|
// 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,
|
|
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);
|
|
}
|
|
|
|
// If there is a stream ref and we weren't canceled,
|
|
// close it away from the main thread.
|
|
// We don't do this when there's an error/cancelation,
|
|
// because our consumer may try to delete the file, which will error
|
|
// if we're still holding on to it, so we have to close it pronto.
|
|
{
|
|
MutexAutoLock lock(data->mStreamMutex);
|
|
if (data->mStream && NS_SUCCEEDED(status) && !mCancel) {
|
|
if (!mBackgroundQueue) {
|
|
nsresult rv = NS_CreateBackgroundTaskQueue(
|
|
"WebBrowserPersist", getter_AddRefs(mBackgroundQueue));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
// Now steal the stream ref and close it away from the main thread,
|
|
// keeping the promise around so we don't finish before all files
|
|
// are flushed and closed.
|
|
mFileClosePromises.AppendElement(InvokeAsync(
|
|
mBackgroundQueue, __func__, [stream = std::move(data->mStream)]() {
|
|
nsresult rv = stream->Close();
|
|
// We don't care if closing failed; we don't care in the
|
|
// destructor either...
|
|
return ClosePromise::CreateAndResolve(rv, __func__);
|
|
}));
|
|
}
|
|
}
|
|
MutexAutoLock lock(mOutputMapMutex);
|
|
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
|
|
//*****************************************************************************
|
|
|
|
// Note: this is supposed to (but not guaranteed to) fire on a background
|
|
// thread when used to save to local disk (channels not using local files will
|
|
// use the main thread).
|
|
// (Read) Access to mOutputMap is guarded via mOutputMapMutex.
|
|
// Access to individual OutputData::mStream is guarded via its mStreamMutex.
|
|
// mCancel is atomic, as is mPersistFlags (accessed via MakeOutputStream).
|
|
// If you end up touching this method and needing other member access, bear
|
|
// this in mind.
|
|
NS_IMETHODIMP
|
|
nsWebBrowserPersist::OnDataAvailable(nsIRequest* request,
|
|
nsIInputStream* aIStream, uint64_t aOffset,
|
|
uint32_t aLength) {
|
|
// MOZ_ASSERT(!NS_IsMainThread()); // no guarantees, but it's likely.
|
|
|
|
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);
|
|
|
|
MutexAutoLock lock(mOutputMapMutex);
|
|
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;
|
|
|
|
MutexAutoLock streamLock(data->mStreamMutex);
|
|
// 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
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"Uploads should be on the main thread.");
|
|
rv = StartUpload(storStream, data->mFile, contentType);
|
|
if (NS_FAILED(rv)) {
|
|
readError = false;
|
|
cancel = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify listener if an error occurred.
|
|
if (cancel) {
|
|
RefPtr<nsIRequest> req = readError ? request : nullptr;
|
|
nsCOMPtr<nsIURI> file = data->mFile;
|
|
RefPtr<Runnable> errorOnMainThread = NS_NewRunnableFunction(
|
|
"nsWebBrowserPersist::SendErrorStatusChange",
|
|
[self = RefPtr{this}, req, file, readError, rv]() {
|
|
self->SendErrorStatusChange(readError, rv, req, file);
|
|
});
|
|
NS_DispatchToMainThread(errorOnMainThread);
|
|
|
|
// And end the download on the main thread.
|
|
nsCOMPtr<nsIRunnable> endOnMainThread = NewRunnableMethod<nsresult>(
|
|
"nsWebBrowserPersist::EndDownload", this,
|
|
&nsWebBrowserPersist::EndDownload, NS_BINDING_ABORTED);
|
|
NS_DispatchToMainThread(endOnMainThread);
|
|
}
|
|
}
|
|
|
|
return cancel ? NS_BINDING_ABORTED : NS_OK;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// nsWebBrowserPersist::nsIThreadRetargetableStreamListener
|
|
//*****************************************************************************
|
|
|
|
NS_IMETHODIMP nsWebBrowserPersist::CheckListenerChain() { return NS_OK; }
|
|
|
|
//*****************************************************************************
|
|
// nsWebBrowserPersist::nsIProgressEventSink
|
|
//*****************************************************************************
|
|
|
|
NS_IMETHODIMP nsWebBrowserPersist::OnProgress(nsIRequest* request,
|
|
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, aProgress, aProgressMax);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP nsWebBrowserPersist::OnStatus(nsIRequest* request,
|
|
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_CONNECTING_TO:
|
|
case NS_NET_STATUS_CONNECTED_TO:
|
|
case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
|
|
case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
|
|
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, 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));
|
|
AutoTArray<nsString, 1> strings;
|
|
nsresult rv;
|
|
if (file) {
|
|
file->GetPath(*strings.AppendElement());
|
|
} else {
|
|
nsAutoCString fileurl;
|
|
rv = aURI->GetSpec(fileurl);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
CopyUTF8toUTF16(fileurl, *strings.AppendElement());
|
|
}
|
|
|
|
const char* msgId;
|
|
switch (aResult) {
|
|
case NS_ERROR_FILE_NAME_TOO_LONG:
|
|
// File name too long.
|
|
msgId = "fileNameTooLongError";
|
|
break;
|
|
case NS_ERROR_FILE_ALREADY_EXISTS:
|
|
// File exists with same name as directory.
|
|
msgId = "fileAlreadyExistsError";
|
|
break;
|
|
case NS_ERROR_FILE_NO_DEVICE_SPACE:
|
|
// Out of space on target volume.
|
|
msgId = "diskFull";
|
|
break;
|
|
|
|
case NS_ERROR_FILE_READ_ONLY:
|
|
// Attempt to write to read/only file.
|
|
msgId = "readOnly";
|
|
break;
|
|
|
|
case NS_ERROR_FILE_ACCESS_DENIED:
|
|
// Attempt to write without sufficient permissions.
|
|
msgId = "accessError";
|
|
break;
|
|
|
|
default:
|
|
// Generic read/write error message.
|
|
if (aIsReadError)
|
|
msgId = "readError";
|
|
else
|
|
msgId = "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);
|
|
|
|
nsAutoString msgText;
|
|
rv = bundle->FormatStringFromName(msgId, strings, msgText);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
|
|
|
mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText.get());
|
|
|
|
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,
|
|
nsCOMPtr<nsIURI>& aOutURI) {
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
|
|
nsAutoCString newPath;
|
|
nsresult rv = aURI->GetPathQueryRef(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);
|
|
|
|
return NS_MutateURI(aURI).SetPathQueryRef(newPath).Finalize(aOutURI);
|
|
}
|
|
|
|
nsresult nsWebBrowserPersist::SaveURIInternal(
|
|
nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
|
|
nsContentPolicyType aContentPolicyType, uint32_t aCacheKey,
|
|
nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
|
|
nsIInputStream* aPostData, const char* aExtraHeaders, nsIURI* aFile,
|
|
bool aCalcFileExt, bool aIsPrivate) {
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
NS_ENSURE_ARG_POINTER(aFile);
|
|
NS_ENSURE_ARG_POINTER(aTriggeringPrincipal);
|
|
|
|
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;
|
|
}
|
|
|
|
// If there is no cookieJarSetting given, we need to create a new
|
|
// cookieJarSettings for this download in order to send cookies based on the
|
|
// current state of the prefs/permissions.
|
|
nsCOMPtr<nsICookieJarSettings> cookieJarSettings = aCookieJarSettings;
|
|
if (!cookieJarSettings) {
|
|
// Although the variable is called 'triggering principal', it is used as the
|
|
// loading principal in the download channel, so we treat it as a loading
|
|
// principal also.
|
|
bool shouldResistFingerprinting =
|
|
nsContentUtils::ShouldResistFingerprinting_dangerous(
|
|
aTriggeringPrincipal,
|
|
"We are creating a new CookieJar Settings, so none exists "
|
|
"currently. Although the variable is called 'triggering principal',"
|
|
"it is used as the loading principal in the download channel, so we"
|
|
"treat it as a loading principal also.");
|
|
cookieJarSettings =
|
|
aIsPrivate
|
|
? net::CookieJarSettings::Create(net::CookieJarSettings::ePrivate,
|
|
shouldResistFingerprinting)
|
|
: net::CookieJarSettings::Create(net::CookieJarSettings::eRegular,
|
|
shouldResistFingerprinting);
|
|
}
|
|
|
|
// Open a channel to the URI
|
|
nsCOMPtr<nsIChannel> inputChannel;
|
|
rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI, aTriggeringPrincipal,
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
|
|
aContentPolicyType, cookieJarSettings,
|
|
nullptr, // aPerformanceStorage
|
|
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);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = inputChannel->LoadInfo();
|
|
loadInfo->SetIsUserTriggeredSave(true);
|
|
|
|
// Set the referrer, post data and headers if any
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
|
|
if (httpChannel) {
|
|
if (aReferrerInfo) {
|
|
DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
|
|
MOZ_ASSERT(NS_SUCCEEDED(success));
|
|
}
|
|
|
|
// 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, ""_ns, -1);
|
|
}
|
|
}
|
|
|
|
// Cache key
|
|
nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
|
|
if (cacheChannel && aCacheKey != 0) {
|
|
cacheChannel->SetCacheKey(aCacheKey);
|
|
}
|
|
|
|
// 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");
|
|
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_MaybeOpenChannelUsingOpen(aChannel, getter_AddRefs(fileInputStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream),
|
|
fileInputStream.forget(),
|
|
BUFFERED_OUTPUT_SIZE);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsAutoCString contentType;
|
|
aChannel->GetContentType(contentType);
|
|
return StartUpload(bufferedInputStream, aFile, contentType);
|
|
}
|
|
|
|
// Mark save channel as throttleable.
|
|
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
|
|
if (cos) {
|
|
cos->AddClassFlags(nsIClassOfService::Throttleable);
|
|
}
|
|
|
|
// Read from the input channel
|
|
nsresult rv = NS_MaybeOpenChannelUsingAsyncOpen(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;
|
|
}
|
|
|
|
MutexAutoLock lock(mOutputMapMutex);
|
|
// Add the output transport to the output map with the channel as the key
|
|
nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel);
|
|
mOutputMap.InsertOrUpdate(keyPtr,
|
|
MakeUnique<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;
|
|
LossyCopyUTF16toASCII(MakeStringSpan(aContentType), contentType);
|
|
nsAutoCString ext;
|
|
rv = mMIMEService->GetPrimaryExtension(contentType, ""_ns, 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 + 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
|
|
|
|
auto* 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 {
|
|
auto* 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,
|
|
nsContentPolicyType aContentPolicyType) {
|
|
return mParent->StoreURI(aURI, aDoc, aContentPolicyType);
|
|
}
|
|
|
|
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, aDoc, nsIContentPolicy::TYPE_SUBDOCUMENT,
|
|
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, aDoc, uriSpec, data);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsWebBrowserPersist::OnWalk::VisitBrowsingContext(
|
|
nsIWebBrowserPersistDocument* aDoc, BrowsingContext* aContext) {
|
|
RefPtr<dom::CanonicalBrowsingContext> context = aContext->Canonical();
|
|
|
|
if (NS_WARN_IF(!context->GetCurrentWindowGlobal())) {
|
|
EndVisit(nullptr, NS_ERROR_FAILURE);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
UniquePtr<WebBrowserPersistDocumentParent> actor(
|
|
new WebBrowserPersistDocumentParent());
|
|
|
|
nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> receiver =
|
|
new OnRemoteWalk(this, aDoc);
|
|
actor->SetOnReady(receiver);
|
|
|
|
RefPtr<dom::BrowserParent> browserParent =
|
|
context->GetCurrentWindowGlobal()->GetBrowserParent();
|
|
|
|
bool ok =
|
|
context->GetContentParent()->SendPWebBrowserPersistDocumentConstructor(
|
|
actor.release(), browserParent, context);
|
|
|
|
if (NS_WARN_IF(!ok)) {
|
|
// (The actor will be destroyed on constructor failure.)
|
|
EndVisit(nullptr, NS_ERROR_FAILURE);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
++mPendingDocuments;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
|
|
nsresult aStatus) {
|
|
if (NS_FAILED(mStatus)) {
|
|
return mStatus;
|
|
}
|
|
|
|
if (NS_FAILED(aStatus)) {
|
|
mStatus = aStatus;
|
|
mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
|
|
mParent->EndDownload(aStatus);
|
|
return aStatus;
|
|
}
|
|
|
|
if (--mPendingDocuments) {
|
|
// We're not done yet, wait for more.
|
|
return NS_OK;
|
|
}
|
|
|
|
mParent->FinishSaveDocumentInternal(mFile, mDataPath);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsWebBrowserPersist::OnRemoteWalk::OnDocumentReady(
|
|
nsIWebBrowserPersistDocument* aSubDocument) {
|
|
mVisitor->VisitDocument(mDocument, aSubDocument);
|
|
mVisitor->EndVisit(mDocument, NS_OK);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsWebBrowserPersist::OnRemoteWalk::OnError(nsresult aFailure) {
|
|
mVisitor->EndVisit(nullptr, aFailure);
|
|
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
|
|
auto* cleanupData = new CleanupData;
|
|
cleanupData->mFile = aDataPath;
|
|
cleanupData->mIsDirectory = true;
|
|
mCleanupList.AppendElement(cleanupData);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mWalkStack.Length() > 0) {
|
|
mozilla::UniquePtr<WalkData> toWalk = mWalkStack.PopLastElement();
|
|
// Bounce this off the event loop to avoid stack overflow.
|
|
using WalkStorage = StoreCopyPassByRRef<decltype(toWalk)>;
|
|
auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred;
|
|
nsCOMPtr<nsIRunnable> saveLater = NewRunnableMethod<WalkStorage>(
|
|
"nsWebBrowserPersist::FinishSaveDocumentInternal", this, saveMethod,
|
|
std::move(toWalk));
|
|
NS_DispatchToCurrentThread(saveLater);
|
|
} else {
|
|
// Done walking DOMs; on to the serialization phase.
|
|
SerializeNextFile();
|
|
}
|
|
}
|
|
|
|
void nsWebBrowserPersist::Cleanup() {
|
|
mURIMap.Clear();
|
|
nsClassHashtable<nsISupportsHashKey, OutputData> outputMapCopy;
|
|
{
|
|
MutexAutoLock lock(mOutputMapMutex);
|
|
mOutputMap.SwapElements(outputMapCopy);
|
|
}
|
|
for (const auto& key : outputMapCopy.Keys()) {
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
|
|
if (channel) {
|
|
channel->Cancel(NS_BINDING_ABORTED);
|
|
}
|
|
}
|
|
outputMapCopy.Clear();
|
|
|
|
for (const auto& key : mUploadList.Keys()) {
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(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<nsIDirectoryEnumerator> dirStack;
|
|
int32_t stackSize = 0;
|
|
|
|
// Push the top level enum onto the stack
|
|
nsCOMPtr<nsIDirectoryEnumerator> pos;
|
|
if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos))))
|
|
dirStack.AppendObject(pos);
|
|
|
|
while (isEmptyDirectory && (stackSize = dirStack.Count())) {
|
|
// Pop the last element
|
|
nsCOMPtr<nsIDirectoryEnumerator> curPos;
|
|
curPos = dirStack[stackSize - 1];
|
|
dirStack.RemoveObjectAt(stackSize - 1);
|
|
|
|
nsCOMPtr<nsIFile> child;
|
|
if (NS_FAILED(curPos->GetNextFile(getter_AddRefs(child))) || !child) {
|
|
continue;
|
|
}
|
|
|
|
bool childIsSymlink = false;
|
|
child->IsSymlink(&childIsSymlink);
|
|
bool childIsDir = false;
|
|
child->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<nsIDirectoryEnumerator> childPos;
|
|
child->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<nsIURI>& aOutURI) {
|
|
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 (true) {
|
|
// Make a file name,
|
|
// Foo become foo_001, foo_002, etc.
|
|
// Empty files become _001, _002 etc.
|
|
|
|
if (base.IsEmpty() || duplicateCounter > 1) {
|
|
SmprintfPointer tmp = mozilla::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.get());
|
|
} 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;
|
|
CopyASCIItoUTF16(filename, filenameAsUnichar);
|
|
localFile->SetLeafName(filenameAsUnichar);
|
|
|
|
// Resync the URI with the file after the extension has been appended
|
|
return NS_MutateURI(aURI)
|
|
.Apply(&nsIFileURLMutator::SetFile, localFile)
|
|
.Finalize(aOutURI);
|
|
}
|
|
return NS_MutateURI(url)
|
|
.Apply(&nsIURLMutator::SetFileName, filename, nullptr)
|
|
.Finalize(aOutURI);
|
|
}
|
|
|
|
// TODO (:valentin) This method should always clone aURI
|
|
aOutURI = aURI;
|
|
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) {
|
|
CopyASCIItoUTF16(NS_UnescapeURL(nameFromURL), fileName);
|
|
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 (IsAsciiAlpha(*p) || 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,
|
|
nsCOMPtr<nsIURI>& aOutURI) {
|
|
nsresult rv = NS_OK;
|
|
|
|
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);
|
|
}
|
|
|
|
// Validate the filename
|
|
if (!contentType.IsEmpty()) {
|
|
nsAutoString newFileName;
|
|
if (NS_SUCCEEDED(mMIMEService->GetValidFileName(
|
|
aChannel, contentType, aOriginalURIWithExtension,
|
|
nsIMIMEService::VALIDATE_DEFAULT, newFileName))) {
|
|
nsCOMPtr<nsIFile> localFile;
|
|
GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
|
|
if (localFile) {
|
|
localFile->SetLeafName(newFileName);
|
|
|
|
// Resync the URI with the file after the extension has been appended
|
|
return NS_MutateURI(aURI)
|
|
.Apply(&nsIFileURLMutator::SetFile, localFile)
|
|
.Finalize(aOutURI);
|
|
}
|
|
return NS_MutateURI(aURI)
|
|
.Apply(&nsIURLMutator::SetFileName,
|
|
NS_ConvertUTF16toUTF8(newFileName), nullptr)
|
|
.Finalize(aOutURI);
|
|
}
|
|
}
|
|
|
|
// TODO (:valentin) This method should always clone aURI
|
|
aOutURI = aURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Note: the MakeOutputStream helpers can be called from a background thread.
|
|
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);
|
|
|
|
rv = NS_NewBufferedOutputStream(aOutputStream, fileOutputStream.forget(),
|
|
BUFFERED_OUTPUT_SIZE);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) {
|
|
// Add to cleanup list in event of failure
|
|
auto* cleanupData = new CleanupData;
|
|
cleanupData->mFile = aFile;
|
|
cleanupData->mIsDirectory = false;
|
|
if (NS_IsMainThread()) {
|
|
mCleanupList.AppendElement(cleanupData);
|
|
} else {
|
|
// If we're on a background thread, add the cleanup back on the main
|
|
// thread.
|
|
RefPtr<Runnable> addCleanup = NS_NewRunnableFunction(
|
|
"nsWebBrowserPersist::AddCleanupToList",
|
|
[self = RefPtr{this}, cleanup = std::move(cleanupData)]() {
|
|
self->mCleanupList.AppendElement(cleanup);
|
|
});
|
|
NS_DispatchToMainThread(addCleanup);
|
|
}
|
|
}
|
|
|
|
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() {
|
|
// We call FinishDownload when we run out of things to download for this
|
|
// persist operation, by dispatching this method to the main thread. By now,
|
|
// it's possible that we have been canceled or encountered an error earlier
|
|
// in the download, or something else called EndDownload. In that case, don't
|
|
// re-run EndDownload.
|
|
if (mEndCalled) {
|
|
return;
|
|
}
|
|
EndDownload(NS_OK);
|
|
}
|
|
|
|
void nsWebBrowserPersist::EndDownload(nsresult aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should end download on the main thread.");
|
|
|
|
// Really this should just never happen, but if it does, at least avoid
|
|
// no-op notifications or pretending we succeeded if we already failed.
|
|
if (mEndCalled && (NS_SUCCEEDED(aResult) || mPersistResult == aResult)) {
|
|
return;
|
|
}
|
|
|
|
// Store the error code in the result if it is an error
|
|
if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) {
|
|
mPersistResult = aResult;
|
|
}
|
|
|
|
if (mEndCalled) {
|
|
MOZ_ASSERT(!mEndCalled, "Should only end the download once.");
|
|
return;
|
|
}
|
|
mEndCalled = true;
|
|
|
|
ClosePromise::All(GetCurrentSerialEventTarget(), mFileClosePromises)
|
|
->Then(GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr{this}, aResult]() {
|
|
self->EndDownloadInternal(aResult);
|
|
});
|
|
}
|
|
|
|
void nsWebBrowserPersist::EndDownloadInternal(nsresult 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 (nsISupports* key : mOutputMap.Keys()) {
|
|
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) {
|
|
// We only get called from OnStartRequest, so this is always on the
|
|
// main thread. Make sure we don't pull the rug from under anything else.
|
|
MutexAutoLock lock(mOutputMapMutex);
|
|
// If a match was found, remove the data entry with the old channel
|
|
// key and re-add it with the new channel key.
|
|
mozilla::UniquePtr<OutputData> outputData;
|
|
mOutputMap.Remove(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.InsertOrUpdate(keyPtr, std::move(outputData));
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsWebBrowserPersist::CalcTotalProgress() {
|
|
mTotalCurrentProgress = 0;
|
|
mTotalMaxProgress = 0;
|
|
|
|
if (mOutputMap.Count() > 0) {
|
|
// Total up the progress of each output stream
|
|
for (const auto& data : mOutputMap.Values()) {
|
|
// Only count toward total progress if destination file is local.
|
|
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 (const auto& data : mUploadList.Values()) {
|
|
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 nsACString& aURI,
|
|
nsIWebBrowserPersistDocument* aDoc,
|
|
nsContentPolicyType aContentPolicyType,
|
|
bool aNeedsPersisting, URIData** aData) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mCurrentCharset.get(),
|
|
mCurrentBaseURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return StoreURI(uri, aDoc, aContentPolicyType, aNeedsPersisting, aData);
|
|
}
|
|
|
|
nsresult nsWebBrowserPersist::StoreURI(nsIURI* aURI,
|
|
nsIWebBrowserPersistDocument* aDoc,
|
|
nsContentPolicyType aContentPolicyType,
|
|
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, aDoc, aContentPolicyType,
|
|
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) {
|
|
fileAsURI = mFile;
|
|
} else {
|
|
fileAsURI = mDataPath;
|
|
rv = AppendPathToURI(fileAsURI, mFilename, fileAsURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// remove username/password if present
|
|
Unused << NS_MutateURI(fileAsURI).SetUserPass(""_ns).Finalize(fileAsURI);
|
|
|
|
// 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) {
|
|
return do_getDocumentTypeSupportedForEncoding(aContentType);
|
|
}
|
|
|
|
nsresult nsWebBrowserPersist::SaveSubframeContent(
|
|
nsIWebBrowserPersistDocument* aFrameContent,
|
|
nsIWebBrowserPersistDocument* aParentDocument, 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);
|
|
|
|
nsString 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 = mCurrentDataPath;
|
|
rv = AppendPathToURI(frameURI, filenameWithExt, frameURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Work out the path for the subframe data
|
|
nsCOMPtr<nsIURI> frameDataURI = mCurrentDataPath;
|
|
nsAutoString newFrameDataPath(aData->mFilename);
|
|
|
|
// Append _data
|
|
newFrameDataPath.AppendLiteral("_data");
|
|
rv = AppendPathToURI(frameDataURI, newFrameDataPath, frameDataURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Make frame document & data path conformant and unique
|
|
nsCOMPtr<nsIURI> out;
|
|
rv = CalculateUniqueFilename(frameURI, out);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
frameURI = out;
|
|
|
|
rv = CalculateUniqueFilename(frameDataURI, out);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
frameDataURI = out;
|
|
|
|
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(std::move(toWalk));
|
|
} else {
|
|
nsContentPolicyType policyType = nsIContentPolicy::TYPE_OTHER;
|
|
if (StringBeginsWith(contentType, "image/"_ns)) {
|
|
policyType = nsIContentPolicy::TYPE_IMAGE;
|
|
} else if (StringBeginsWith(contentType, "audio/"_ns) ||
|
|
StringBeginsWith(contentType, "video/"_ns)) {
|
|
policyType = nsIContentPolicy::TYPE_MEDIA;
|
|
}
|
|
rv = StoreURI(aURISpec, aParentDocument, policyType);
|
|
}
|
|
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_SEC_CONTEXT_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, nsIWebBrowserPersistDocument* aDoc,
|
|
nsContentPolicyType aContentPolicyType, 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.Get(spec, &data)) {
|
|
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;
|
|
|
|
data->mContentPolicyType = aContentPolicyType;
|
|
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;
|
|
|
|
aDoc->GetPrincipal(getter_AddRefs(data->mTriggeringPrincipal));
|
|
aDoc->GetCookieJarSettings(getter_AddRefs(data->mCookieJarSettings));
|
|
|
|
if (aNeedsPersisting) mCurrentThingsToPersist++;
|
|
|
|
mURIMap.InsertOrUpdate(spec, UniquePtr<URIData>(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);
|
|
}
|
|
}
|
|
}
|