1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/TextUtils.h"
11 #include "nsIFileStreams.h" // New Necko file streams
15 #include "nsNetUtil.h"
16 #include "nsIClassOfService.h"
17 #include "nsIInterfaceRequestorUtils.h"
18 #include "nsILoadContext.h"
19 #include "nsIPrivateBrowsingChannel.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsIStorageStream.h"
22 #include "nsISeekableStream.h"
23 #include "nsIHttpChannel.h"
24 #include "nsIEncodedChannel.h"
25 #include "nsIUploadChannel.h"
26 #include "nsICacheInfoChannel.h"
27 #include "nsIFileChannel.h"
29 #include "nsUnicharUtils.h"
30 #include "nsIStringEnumerator.h"
31 #include "nsContentCID.h"
32 #include "nsStreamUtils.h"
34 #include "nsCExternalHandlerService.h"
37 #include "nsIFileURL.h"
38 #include "nsIWebProgressListener.h"
39 #include "nsIAuthPrompt.h"
40 #include "nsIPrompt.h"
41 #include "nsIFormControl.h"
42 #include "nsIThreadRetargetableRequest.h"
43 #include "nsContentUtils.h"
45 #include "nsIStringBundle.h"
46 #include "nsIProtocolHandler.h"
48 #include "nsWebBrowserPersist.h"
49 #include "WebBrowserPersistLocalDocument.h"
51 #include "nsIContent.h"
52 #include "nsIMIMEInfo.h"
53 #include "mozilla/dom/HTMLInputElement.h"
54 #include "mozilla/dom/HTMLSharedElement.h"
55 #include "mozilla/net/CookieJarSettings.h"
56 #include "mozilla/Mutex.h"
57 #include "mozilla/Printf.h"
58 #include "ReferrerInfo.h"
59 #include "nsIURIMutator.h"
60 #include "mozilla/WebBrowserPersistDocumentParent.h"
61 #include "mozilla/dom/CanonicalBrowsingContext.h"
62 #include "mozilla/dom/WindowGlobalParent.h"
63 #include "mozilla/dom/ContentParent.h"
64 #include "mozilla/dom/PContentParent.h"
65 #include "mozilla/dom/BrowserParent.h"
66 #include "nsIDocumentEncoder.h"
68 using namespace mozilla
;
69 using namespace mozilla::dom
;
71 // Buffer file writes in 32kb chunks
72 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
74 struct nsWebBrowserPersist::WalkData
{
75 nsCOMPtr
<nsIWebBrowserPersistDocument
> mDocument
;
76 nsCOMPtr
<nsIURI
> mFile
;
77 nsCOMPtr
<nsIURI
> mDataPath
;
80 // Information about a DOM document
81 struct nsWebBrowserPersist::DocData
{
82 nsCOMPtr
<nsIURI
> mBaseURI
;
83 nsCOMPtr
<nsIWebBrowserPersistDocument
> mDocument
;
84 nsCOMPtr
<nsIURI
> mFile
;
88 // Information about a URI
89 struct nsWebBrowserPersist::URIData
{
90 bool mNeedsPersisting
;
93 bool mDataPathIsRelative
;
96 nsString mSubFrameExt
;
97 nsCOMPtr
<nsIURI
> mFile
;
98 nsCOMPtr
<nsIURI
> mDataPath
;
99 nsCOMPtr
<nsIURI
> mRelativeDocumentURI
;
100 nsCOMPtr
<nsIPrincipal
> mTriggeringPrincipal
;
101 nsCOMPtr
<nsICookieJarSettings
> mCookieJarSettings
;
102 nsContentPolicyType mContentPolicyType
;
103 nsCString mRelativePathToData
;
106 nsresult
GetLocalURI(nsIURI
* targetBaseURI
, nsCString
& aSpecOut
);
109 // Information about the output stream
110 // Note that this data structure (and the map that nsWebBrowserPersist keeps,
111 // where these are values) is used from two threads: the main thread,
112 // and the background task thread.
113 // The background thread only writes to mStream (from OnDataAvailable), and
114 // this access is guarded using mStreamMutex. It reads the mFile member, which
115 // is only written to on the main thread when the object is constructed and
116 // from OnStartRequest (if mCalcFileExt), both guaranteed to happen before
117 // OnDataAvailable is fired.
118 // The main thread gets OnStartRequest, OnStopRequest, and progress sink events,
119 // and accesses the other members.
120 struct nsWebBrowserPersist::OutputData
{
121 nsCOMPtr
<nsIURI
> mFile
;
122 nsCOMPtr
<nsIURI
> mOriginalLocation
;
123 nsCOMPtr
<nsIOutputStream
> mStream
;
125 int64_t mSelfProgress
;
126 int64_t mSelfProgressMax
;
129 OutputData(nsIURI
* aFile
, nsIURI
* aOriginalLocation
, bool aCalcFileExt
)
131 mOriginalLocation(aOriginalLocation
),
132 mStreamMutex("nsWebBrowserPersist::OutputData::mStreamMutex"),
134 mSelfProgressMax(10000),
135 mCalcFileExt(aCalcFileExt
) {}
137 // Gaining this lock in the destructor is pretty icky. It should be OK
138 // because the only other place we lock the mutex is in OnDataAvailable,
139 // which will never itself cause the OutputData instance to be
141 MutexAutoLock
lock(mStreamMutex
);
148 struct nsWebBrowserPersist::UploadData
{
149 nsCOMPtr
<nsIURI
> mFile
;
150 int64_t mSelfProgress
;
151 int64_t mSelfProgressMax
;
153 explicit UploadData(nsIURI
* aFile
)
154 : mFile(aFile
), mSelfProgress(0), mSelfProgressMax(10000) {}
157 struct nsWebBrowserPersist::CleanupData
{
158 nsCOMPtr
<nsIFile
> mFile
;
159 // Snapshot of what the file actually is at the time of creation so that if
160 // it transmutes into something else later on it can be ignored. For example,
161 // catch files that turn into dirs or vice versa.
165 class nsWebBrowserPersist::OnWalk final
166 : public nsIWebBrowserPersistResourceVisitor
{
168 OnWalk(nsWebBrowserPersist
* aParent
, nsIURI
* aFile
, nsIFile
* aDataPath
)
171 mDataPath(aDataPath
),
172 mPendingDocuments(1),
175 NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
178 RefPtr
<nsWebBrowserPersist
> mParent
;
179 nsCOMPtr
<nsIURI
> mFile
;
180 nsCOMPtr
<nsIFile
> mDataPath
;
182 uint32_t mPendingDocuments
;
185 virtual ~OnWalk() = default;
188 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk
,
189 nsIWebBrowserPersistResourceVisitor
)
191 class nsWebBrowserPersist::OnRemoteWalk final
192 : public nsIWebBrowserPersistDocumentReceiver
{
194 OnRemoteWalk(nsIWebBrowserPersistResourceVisitor
* aVisitor
,
195 nsIWebBrowserPersistDocument
* aDocument
)
196 : mVisitor(aVisitor
), mDocument(aDocument
) {}
198 NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
201 nsCOMPtr
<nsIWebBrowserPersistResourceVisitor
> mVisitor
;
202 nsCOMPtr
<nsIWebBrowserPersistDocument
> mDocument
;
204 virtual ~OnRemoteWalk() = default;
207 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnRemoteWalk
,
208 nsIWebBrowserPersistDocumentReceiver
)
210 class nsWebBrowserPersist::OnWrite final
211 : public nsIWebBrowserPersistWriteCompletion
{
213 OnWrite(nsWebBrowserPersist
* aParent
, nsIURI
* aFile
, nsIFile
* aLocalFile
)
214 : mParent(aParent
), mFile(aFile
), mLocalFile(aLocalFile
) {}
216 NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
219 RefPtr
<nsWebBrowserPersist
> mParent
;
220 nsCOMPtr
<nsIURI
> mFile
;
221 nsCOMPtr
<nsIFile
> mLocalFile
;
223 virtual ~OnWrite() = default;
226 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite
,
227 nsIWebBrowserPersistWriteCompletion
)
229 class nsWebBrowserPersist::FlatURIMap final
230 : public nsIWebBrowserPersistURIMap
{
232 explicit FlatURIMap(const nsACString
& aTargetBase
)
233 : mTargetBase(aTargetBase
) {}
235 void Add(const nsACString
& aMapFrom
, const nsACString
& aMapTo
) {
236 mMapFrom
.AppendElement(aMapFrom
);
237 mMapTo
.AppendElement(aMapTo
);
240 NS_DECL_NSIWEBBROWSERPERSISTURIMAP
244 nsTArray
<nsCString
> mMapFrom
;
245 nsTArray
<nsCString
> mMapTo
;
246 nsCString mTargetBase
;
248 virtual ~FlatURIMap() = default;
251 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap
, nsIWebBrowserPersistURIMap
)
254 nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum
) {
255 MOZ_ASSERT(mMapFrom
.Length() == mMapTo
.Length());
256 *aNum
= mMapTo
.Length();
261 nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString
& aTargetBase
) {
262 aTargetBase
= mTargetBase
;
267 nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex
,
268 nsACString
& aMapFrom
,
269 nsACString
& aMapTo
) {
270 MOZ_ASSERT(mMapFrom
.Length() == mMapTo
.Length());
271 if (aIndex
>= mMapTo
.Length()) {
272 return NS_ERROR_INVALID_ARG
;
274 aMapFrom
= mMapFrom
[aIndex
];
275 aMapTo
= mMapTo
[aIndex
];
279 // Maximum file length constant. The max file name length is
280 // volume / server dependent but it is difficult to obtain
281 // that information. Instead this constant is a reasonable value that
282 // modern systems should able to cope with.
283 const uint32_t kDefaultMaxFilenameLength
= 64;
285 // Default flags for persistence
286 const uint32_t kDefaultPersistFlags
=
287 nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION
|
288 nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES
;
290 // String bundle where error messages come from
291 const char* kWebBrowserPersistStringBundle
=
292 "chrome://global/locale/nsWebBrowserPersist.properties";
294 nsWebBrowserPersist::nsWebBrowserPersist()
295 : mCurrentDataPathIsRelative(false),
296 mCurrentThingsToPersist(0),
297 mOutputMapMutex("nsWebBrowserPersist::mOutputMapMutex"),
298 mFirstAndOnlyUse(true),
299 mSavingDocument(false),
304 mReplaceExisting(true),
305 mSerializingOutput(false),
307 mPersistFlags(kDefaultPersistFlags
),
308 mPersistResult(NS_OK
),
309 mTotalCurrentProgress(0),
310 mTotalMaxProgress(0),
314 nsWebBrowserPersist::~nsWebBrowserPersist() { Cleanup(); }
316 //*****************************************************************************
317 // nsWebBrowserPersist::nsISupports
318 //*****************************************************************************
320 NS_IMPL_ADDREF(nsWebBrowserPersist
)
321 NS_IMPL_RELEASE(nsWebBrowserPersist
)
323 NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist
)
324 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIWebBrowserPersist
)
325 NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist
)
326 NS_INTERFACE_MAP_ENTRY(nsICancelable
)
327 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor
)
328 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
329 NS_INTERFACE_MAP_ENTRY(nsIStreamListener
)
330 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener
)
331 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver
)
332 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink
)
335 //*****************************************************************************
336 // nsWebBrowserPersist::nsIInterfaceRequestor
337 //*****************************************************************************
339 NS_IMETHODIMP
nsWebBrowserPersist::GetInterface(const nsIID
& aIID
,
341 NS_ENSURE_ARG_POINTER(aIFace
);
345 nsresult rv
= QueryInterface(aIID
, aIFace
);
346 if (NS_SUCCEEDED(rv
)) {
350 if (mProgressListener
&& (aIID
.Equals(NS_GET_IID(nsIAuthPrompt
)) ||
351 aIID
.Equals(NS_GET_IID(nsIPrompt
)))) {
352 mProgressListener
->QueryInterface(aIID
, aIFace
);
353 if (*aIFace
) return NS_OK
;
356 nsCOMPtr
<nsIInterfaceRequestor
> req
= do_QueryInterface(mProgressListener
);
358 return req
->GetInterface(aIID
, aIFace
);
361 return NS_ERROR_NO_INTERFACE
;
364 //*****************************************************************************
365 // nsWebBrowserPersist::nsIWebBrowserPersist
366 //*****************************************************************************
368 NS_IMETHODIMP
nsWebBrowserPersist::GetPersistFlags(uint32_t* aPersistFlags
) {
369 NS_ENSURE_ARG_POINTER(aPersistFlags
);
370 *aPersistFlags
= mPersistFlags
;
373 NS_IMETHODIMP
nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags
) {
374 mPersistFlags
= aPersistFlags
;
375 mReplaceExisting
= (mPersistFlags
& PERSIST_FLAGS_REPLACE_EXISTING_FILES
);
376 mSerializingOutput
= (mPersistFlags
& PERSIST_FLAGS_SERIALIZE_OUTPUT
);
380 NS_IMETHODIMP
nsWebBrowserPersist::GetCurrentState(uint32_t* aCurrentState
) {
381 NS_ENSURE_ARG_POINTER(aCurrentState
);
383 *aCurrentState
= PERSIST_STATE_FINISHED
;
384 } else if (mFirstAndOnlyUse
) {
385 *aCurrentState
= PERSIST_STATE_SAVING
;
387 *aCurrentState
= PERSIST_STATE_READY
;
392 NS_IMETHODIMP
nsWebBrowserPersist::GetResult(nsresult
* aResult
) {
393 NS_ENSURE_ARG_POINTER(aResult
);
394 *aResult
= mPersistResult
;
398 NS_IMETHODIMP
nsWebBrowserPersist::GetProgressListener(
399 nsIWebProgressListener
** aProgressListener
) {
400 NS_ENSURE_ARG_POINTER(aProgressListener
);
401 *aProgressListener
= mProgressListener
;
402 NS_IF_ADDREF(*aProgressListener
);
406 NS_IMETHODIMP
nsWebBrowserPersist::SetProgressListener(
407 nsIWebProgressListener
* aProgressListener
) {
408 mProgressListener
= aProgressListener
;
409 mProgressListener2
= do_QueryInterface(aProgressListener
);
410 mEventSink
= do_GetInterface(aProgressListener
);
414 NS_IMETHODIMP
nsWebBrowserPersist::SaveURI(
415 nsIURI
* aURI
, nsIPrincipal
* aPrincipal
, uint32_t aCacheKey
,
416 nsIReferrerInfo
* aReferrerInfo
, nsICookieJarSettings
* aCookieJarSettings
,
417 nsIInputStream
* aPostData
, const char* aExtraHeaders
, nsISupports
* aFile
,
418 nsContentPolicyType aContentPolicyType
, nsILoadContext
* aPrivacyContext
) {
419 bool isPrivate
= aPrivacyContext
&& aPrivacyContext
->UsePrivateBrowsing();
420 return SavePrivacyAwareURI(aURI
, aPrincipal
, aCacheKey
, aReferrerInfo
,
421 aCookieJarSettings
, aPostData
, aExtraHeaders
,
422 aFile
, aContentPolicyType
, isPrivate
);
425 NS_IMETHODIMP
nsWebBrowserPersist::SavePrivacyAwareURI(
426 nsIURI
* aURI
, nsIPrincipal
* aPrincipal
, uint32_t aCacheKey
,
427 nsIReferrerInfo
* aReferrerInfo
, nsICookieJarSettings
* aCookieJarSettings
,
428 nsIInputStream
* aPostData
, const char* aExtraHeaders
, nsISupports
* aFile
,
429 nsContentPolicyType aContentPolicy
, bool aIsPrivate
) {
430 NS_ENSURE_TRUE(mFirstAndOnlyUse
, NS_ERROR_FAILURE
);
431 mFirstAndOnlyUse
= false; // Stop people from reusing this object!
433 nsCOMPtr
<nsIURI
> fileAsURI
;
435 rv
= GetValidURIFromObject(aFile
, getter_AddRefs(fileAsURI
));
436 NS_ENSURE_SUCCESS(rv
, NS_ERROR_INVALID_ARG
);
438 // SaveURI doesn't like broken uris.
439 mPersistFlags
|= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS
;
440 rv
= SaveURIInternal(aURI
, aPrincipal
, aContentPolicy
, aCacheKey
,
441 aReferrerInfo
, aCookieJarSettings
, aPostData
,
442 aExtraHeaders
, fileAsURI
, false, aIsPrivate
);
443 return NS_FAILED(rv
) ? rv
: NS_OK
;
446 NS_IMETHODIMP
nsWebBrowserPersist::SaveChannel(nsIChannel
* aChannel
,
447 nsISupports
* aFile
) {
448 NS_ENSURE_TRUE(mFirstAndOnlyUse
, NS_ERROR_FAILURE
);
449 mFirstAndOnlyUse
= false; // Stop people from reusing this object!
451 nsCOMPtr
<nsIURI
> fileAsURI
;
453 rv
= GetValidURIFromObject(aFile
, getter_AddRefs(fileAsURI
));
454 NS_ENSURE_SUCCESS(rv
, NS_ERROR_INVALID_ARG
);
456 rv
= aChannel
->GetURI(getter_AddRefs(mURI
));
457 NS_ENSURE_SUCCESS(rv
, rv
);
459 // SaveURI doesn't like broken uris.
460 mPersistFlags
|= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS
;
461 rv
= SaveChannelInternal(aChannel
, fileAsURI
, false);
462 return NS_FAILED(rv
) ? rv
: NS_OK
;
465 NS_IMETHODIMP
nsWebBrowserPersist::SaveDocument(nsISupports
* aDocument
,
467 nsISupports
* aDataPath
,
468 const char* aOutputContentType
,
469 uint32_t aEncodingFlags
,
470 uint32_t aWrapColumn
) {
471 NS_ENSURE_TRUE(mFirstAndOnlyUse
, NS_ERROR_FAILURE
);
472 mFirstAndOnlyUse
= false; // Stop people from reusing this object!
474 // We need a STATE_IS_NETWORK start/stop pair to bracket the
475 // notification callbacks. For a whole document we generate those
476 // here and in EndDownload(), but for the single-request methods
477 // that's done in On{Start,Stop}Request instead.
478 mSavingDocument
= true;
480 NS_ENSURE_ARG_POINTER(aDocument
);
481 NS_ENSURE_ARG_POINTER(aFile
);
483 nsCOMPtr
<nsIURI
> fileAsURI
;
484 nsCOMPtr
<nsIURI
> datapathAsURI
;
487 rv
= GetValidURIFromObject(aFile
, getter_AddRefs(fileAsURI
));
488 NS_ENSURE_SUCCESS(rv
, NS_ERROR_INVALID_ARG
);
490 rv
= GetValidURIFromObject(aDataPath
, getter_AddRefs(datapathAsURI
));
491 NS_ENSURE_SUCCESS(rv
, NS_ERROR_INVALID_ARG
);
494 mWrapColumn
= aWrapColumn
;
495 mEncodingFlags
= aEncodingFlags
;
497 if (aOutputContentType
) {
498 mContentType
.AssignASCII(aOutputContentType
);
501 // State start notification
502 if (mProgressListener
) {
503 mProgressListener
->OnStateChange(
505 nsIWebProgressListener::STATE_START
|
506 nsIWebProgressListener::STATE_IS_NETWORK
,
510 nsCOMPtr
<nsIWebBrowserPersistDocument
> doc
= do_QueryInterface(aDocument
);
512 nsCOMPtr
<Document
> localDoc
= do_QueryInterface(aDocument
);
514 doc
= new mozilla::WebBrowserPersistLocalDocument(localDoc
);
516 rv
= NS_ERROR_NO_INTERFACE
;
521 if (doc
&& NS_SUCCEEDED(doc
->GetIsClosed(&closed
)) && !closed
) {
522 rv
= SaveDocumentInternal(doc
, fileAsURI
, datapathAsURI
);
525 if (NS_FAILED(rv
) || closed
) {
526 SendErrorStatusChange(true, rv
, nullptr, mURI
);
532 NS_IMETHODIMP
nsWebBrowserPersist::Cancel(nsresult aReason
) {
533 // No point cancelling if we're already complete.
538 EndDownload(aReason
);
542 NS_IMETHODIMP
nsWebBrowserPersist::CancelSave() {
543 return Cancel(NS_BINDING_ABORTED
);
546 nsresult
nsWebBrowserPersist::StartUpload(nsIStorageStream
* storStream
,
547 nsIURI
* aDestinationURI
,
548 const nsACString
& aContentType
) {
549 // setup the upload channel if the destination is not local
550 nsCOMPtr
<nsIInputStream
> inputstream
;
551 nsresult rv
= storStream
->NewInputStream(0, getter_AddRefs(inputstream
));
552 NS_ENSURE_TRUE(inputstream
, NS_ERROR_FAILURE
);
553 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
554 return StartUpload(inputstream
, aDestinationURI
, aContentType
);
557 nsresult
nsWebBrowserPersist::StartUpload(nsIInputStream
* aInputStream
,
558 nsIURI
* aDestinationURI
,
559 const nsACString
& aContentType
) {
560 nsCOMPtr
<nsIChannel
> destChannel
;
561 CreateChannelFromURI(aDestinationURI
, getter_AddRefs(destChannel
));
562 nsCOMPtr
<nsIUploadChannel
> uploadChannel(do_QueryInterface(destChannel
));
563 NS_ENSURE_TRUE(uploadChannel
, NS_ERROR_FAILURE
);
565 // Set the upload stream
566 // NOTE: ALL data must be available in "inputstream"
567 nsresult rv
= uploadChannel
->SetUploadStream(aInputStream
, aContentType
, -1);
568 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
569 rv
= destChannel
->AsyncOpen(this);
570 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
572 // add this to the upload list
573 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(destChannel
);
574 mUploadList
.InsertOrUpdate(keyPtr
, MakeUnique
<UploadData
>(aDestinationURI
));
579 void nsWebBrowserPersist::SerializeNextFile() {
581 MOZ_ASSERT(mWalkStack
.Length() == 0);
583 // First, handle gathered URIs.
584 // This is potentially O(n^2), when taking into account the
585 // number of times this method is called. If it becomes a
586 // bottleneck, the count of not-yet-persisted URIs could be
587 // maintained separately, and we can skip iterating mURIMap if there are none.
589 // Persist each file in the uri map. The document(s)
590 // will be saved after the last one of these is saved.
591 for (const auto& entry
: mURIMap
) {
592 URIData
* data
= entry
.GetWeak();
594 if (!data
->mNeedsPersisting
|| data
->mSaved
) {
598 // Create a URI from the key.
599 nsCOMPtr
<nsIURI
> uri
;
600 rv
= NS_NewURI(getter_AddRefs(uri
), entry
.GetKey(), data
->mCharset
.get());
601 if (NS_WARN_IF(NS_FAILED(rv
))) {
605 // Make a URI to save the data to.
606 nsCOMPtr
<nsIURI
> fileAsURI
= data
->mDataPath
;
607 rv
= AppendPathToURI(fileAsURI
, data
->mFilename
, fileAsURI
);
608 if (NS_WARN_IF(NS_FAILED(rv
))) {
612 rv
= SaveURIInternal(uri
, data
->mTriggeringPrincipal
,
613 data
->mContentPolicyType
, 0, nullptr,
614 data
->mCookieJarSettings
, nullptr, nullptr, fileAsURI
,
616 // If SaveURIInternal fails, then it will have called EndDownload,
617 // which means that |data| is no longer valid memory. We MUST bail.
618 if (NS_WARN_IF(NS_FAILED(rv
))) {
623 // URIData.mFile will be updated to point to the correct
624 // URI object when it is fixed up with the right file extension
626 data
->mFile
= fileAsURI
;
629 data
->mNeedsFixup
= false;
632 if (mSerializingOutput
) {
637 // If there are downloads happening, wait until they're done; the
638 // OnStopRequest handler will call this method again.
639 if (mOutputMap
.Count() > 0) {
643 // If serializing, also wait until last upload is done.
644 if (mSerializingOutput
&& mUploadList
.Count() > 0) {
648 // If there are also no more documents, then we're done.
649 if (mDocList
.Length() == 0) {
650 // ...or not quite done, if there are still uploads.
651 if (mUploadList
.Count() > 0) {
654 // Finish and clean things up. Defer this because the caller
655 // may have been expecting to use the listeners that that
656 // method will clear.
657 NS_DispatchToCurrentThread(
658 NewRunnableMethod("nsWebBrowserPersist::FinishDownload", this,
659 &nsWebBrowserPersist::FinishDownload
));
663 // There are no URIs to save, so just save the next document.
665 mozilla::UniquePtr
<DocData
> docData(mDocList
.ElementAt(0));
666 mDocList
.RemoveElementAt(0); // O(n^2) but probably doesn't matter.
669 EndDownload(NS_ERROR_FAILURE
);
673 mCurrentBaseURI
= docData
->mBaseURI
;
674 mCurrentCharset
= docData
->mCharset
;
675 mTargetBaseURI
= docData
->mFile
;
677 // Save the document, fixing it up with the new URIs as we do
679 nsAutoCString targetBaseSpec
;
680 if (mTargetBaseURI
) {
681 rv
= mTargetBaseURI
->GetSpec(targetBaseSpec
);
683 SendErrorStatusChange(true, rv
, nullptr, nullptr);
689 // mFlatURIMap must be rebuilt each time through SerializeNextFile, as
690 // mTargetBaseURI is used to create the relative URLs and will be different
691 // with each serialized document.
692 RefPtr
<FlatURIMap
> flatMap
= new FlatURIMap(targetBaseSpec
);
693 for (const auto& uriEntry
: mURIMap
) {
695 nsresult rv
= uriEntry
.GetWeak()->GetLocalURI(mTargetBaseURI
, mapTo
);
696 if (NS_SUCCEEDED(rv
) || !mapTo
.IsVoid()) {
697 flatMap
->Add(uriEntry
.GetKey(), mapTo
);
700 mFlatURIMap
= std::move(flatMap
);
702 nsCOMPtr
<nsIFile
> localFile
;
703 GetLocalFileFromURI(docData
->mFile
, getter_AddRefs(localFile
));
705 // if we're not replacing an existing file but the file
706 // exists, something is wrong
707 bool fileExists
= false;
708 rv
= localFile
->Exists(&fileExists
);
709 if (NS_SUCCEEDED(rv
) && !mReplaceExisting
&& fileExists
) {
710 rv
= NS_ERROR_FILE_ALREADY_EXISTS
;
713 SendErrorStatusChange(false, rv
, nullptr, docData
->mFile
);
718 nsCOMPtr
<nsIOutputStream
> outputStream
;
719 rv
= MakeOutputStream(docData
->mFile
, getter_AddRefs(outputStream
));
720 if (NS_SUCCEEDED(rv
) && !outputStream
) {
721 rv
= NS_ERROR_FAILURE
;
724 SendErrorStatusChange(false, rv
, nullptr, docData
->mFile
);
729 RefPtr
<OnWrite
> finish
= new OnWrite(this, docData
->mFile
, localFile
);
730 rv
= docData
->mDocument
->WriteContent(outputStream
, mFlatURIMap
,
731 NS_ConvertUTF16toUTF8(mContentType
),
732 mEncodingFlags
, mWrapColumn
, finish
);
734 SendErrorStatusChange(false, rv
, nullptr, docData
->mFile
);
740 nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument
* aDoc
,
741 nsIOutputStream
* aStream
,
742 const nsACString
& aContentType
,
744 nsresult rv
= aStatus
;
747 mParent
->SendErrorStatusChange(false, rv
, nullptr, mFile
);
748 mParent
->EndDownload(rv
);
752 nsCOMPtr
<nsIStorageStream
> storStream(do_QueryInterface(aStream
));
755 rv
= mParent
->StartUpload(storStream
, mFile
, aContentType
);
757 mParent
->SendErrorStatusChange(false, rv
, nullptr, mFile
);
758 mParent
->EndDownload(rv
);
760 // Either we failed and we're done, or we're uploading and
761 // the OnStopRequest callback is responsible for the next
762 // SerializeNextFile().
766 NS_DispatchToCurrentThread(
767 NewRunnableMethod("nsWebBrowserPersist::SerializeNextFile", mParent
,
768 &nsWebBrowserPersist::SerializeNextFile
));
772 //*****************************************************************************
773 // nsWebBrowserPersist::nsIRequestObserver
774 //*****************************************************************************
776 NS_IMETHODIMP
nsWebBrowserPersist::OnStartRequest(nsIRequest
* request
) {
777 if (mProgressListener
) {
778 uint32_t stateFlags
= nsIWebProgressListener::STATE_START
|
779 nsIWebProgressListener::STATE_IS_REQUEST
;
780 if (!mSavingDocument
) {
781 stateFlags
|= nsIWebProgressListener::STATE_IS_NETWORK
;
783 mProgressListener
->OnStateChange(nullptr, request
, stateFlags
, NS_OK
);
786 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
787 NS_ENSURE_TRUE(channel
, NS_ERROR_FAILURE
);
789 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(request
);
790 OutputData
* data
= mOutputMap
.Get(keyPtr
);
792 // NOTE: This code uses the channel as a hash key so it will not
793 // recognize redirected channels because the key is not the same.
794 // When that happens we remove and add the data entry to use the
795 // new channel as the hash key.
797 UploadData
* upData
= mUploadList
.Get(keyPtr
);
799 // Redirect? Try and fixup the output table
800 nsresult rv
= FixRedirectedChannelEntry(channel
);
801 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
803 // Should be able to find the data after fixup unless redirects
805 data
= mOutputMap
.Get(keyPtr
);
807 return NS_ERROR_FAILURE
;
812 if (data
&& data
->mFile
) {
813 nsCOMPtr
<nsIThreadRetargetableRequest
> r
= do_QueryInterface(request
);
814 // Determine if we're uploading. Only use OMT onDataAvailable if not.
815 nsCOMPtr
<nsIFile
> localFile
;
816 GetLocalFileFromURI(data
->mFile
, getter_AddRefs(localFile
));
817 if (r
&& localFile
) {
818 if (!mBackgroundQueue
) {
819 NS_CreateBackgroundTaskQueue("WebBrowserPersist",
820 getter_AddRefs(mBackgroundQueue
));
822 if (mBackgroundQueue
) {
823 r
->RetargetDeliveryTo(mBackgroundQueue
);
827 // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags,
828 // try to determine whether this channel needs to apply Content-Encoding
831 !((mPersistFlags
& PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION
) &&
832 (mPersistFlags
& PERSIST_FLAGS_NO_CONVERSION
)),
833 "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set");
834 if (mPersistFlags
& PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION
)
835 SetApplyConversionIfNeeded(channel
);
837 if (data
->mCalcFileExt
&&
838 !(mPersistFlags
& PERSIST_FLAGS_DONT_CHANGE_FILENAMES
)) {
839 nsCOMPtr
<nsIURI
> uriWithExt
;
840 // this is the first point at which the server can tell us the mimetype
841 nsresult rv
= CalculateAndAppendFileExt(
842 data
->mFile
, channel
, data
->mOriginalLocation
, uriWithExt
);
843 if (NS_SUCCEEDED(rv
)) {
844 data
->mFile
= uriWithExt
;
847 // now make filename conformant and unique
848 nsCOMPtr
<nsIURI
> uniqueFilenameURI
;
849 rv
= CalculateUniqueFilename(data
->mFile
, uniqueFilenameURI
);
850 if (NS_SUCCEEDED(rv
)) {
851 data
->mFile
= uniqueFilenameURI
;
854 // The URIData entry is pointing to the old unfixed URI, so we need
856 nsCOMPtr
<nsIURI
> chanURI
;
857 rv
= channel
->GetOriginalURI(getter_AddRefs(chanURI
));
858 if (NS_SUCCEEDED(rv
)) {
860 chanURI
->GetSpec(spec
);
862 if (mURIMap
.Get(spec
, &uridata
)) {
863 uridata
->mFile
= data
->mFile
;
868 // compare uris and bail before we add to output map if they are equal
869 bool isEqual
= false;
870 if (NS_SUCCEEDED(data
->mFile
->Equals(data
->mOriginalLocation
, &isEqual
)) &&
873 MutexAutoLock
lock(mOutputMapMutex
);
874 // remove from output map
875 mOutputMap
.Remove(keyPtr
);
878 // cancel; we don't need to know any more
879 // stop request will get called
880 request
->Cancel(NS_BINDING_ABORTED
);
887 NS_IMETHODIMP
nsWebBrowserPersist::OnStopRequest(nsIRequest
* request
,
889 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(request
);
890 OutputData
* data
= mOutputMap
.Get(keyPtr
);
892 if (NS_SUCCEEDED(mPersistResult
) && NS_FAILED(status
)) {
893 SendErrorStatusChange(true, status
, request
, data
->mFile
);
896 // If there is a stream ref and we weren't canceled,
897 // close it away from the main thread.
898 // We don't do this when there's an error/cancelation,
899 // because our consumer may try to delete the file, which will error
900 // if we're still holding on to it, so we have to close it pronto.
902 MutexAutoLock
lock(data
->mStreamMutex
);
903 if (data
->mStream
&& NS_SUCCEEDED(status
) && !mCancel
) {
904 if (!mBackgroundQueue
) {
905 nsresult rv
= NS_CreateBackgroundTaskQueue(
906 "WebBrowserPersist", getter_AddRefs(mBackgroundQueue
));
911 // Now steal the stream ref and close it away from the main thread,
912 // keeping the promise around so we don't finish before all files
913 // are flushed and closed.
914 mFileClosePromises
.AppendElement(InvokeAsync(
915 mBackgroundQueue
, __func__
, [stream
= std::move(data
->mStream
)]() {
916 nsresult rv
= stream
->Close();
917 // We don't care if closing failed; we don't care in the
918 // destructor either...
919 return ClosePromise::CreateAndResolve(rv
, __func__
);
923 MutexAutoLock
lock(mOutputMapMutex
);
924 mOutputMap
.Remove(keyPtr
);
926 // if we didn't find the data in mOutputMap, try mUploadList
927 UploadData
* upData
= mUploadList
.Get(keyPtr
);
929 mUploadList
.Remove(keyPtr
);
936 if (mProgressListener
) {
937 uint32_t stateFlags
= nsIWebProgressListener::STATE_STOP
|
938 nsIWebProgressListener::STATE_IS_REQUEST
;
939 if (!mSavingDocument
) {
940 stateFlags
|= nsIWebProgressListener::STATE_IS_NETWORK
;
942 mProgressListener
->OnStateChange(nullptr, request
, stateFlags
, status
);
948 //*****************************************************************************
949 // nsWebBrowserPersist::nsIStreamListener
950 //*****************************************************************************
952 // Note: this is supposed to (but not guaranteed to) fire on a background
953 // thread when used to save to local disk (channels not using local files will
954 // use the main thread).
955 // (Read) Access to mOutputMap is guarded via mOutputMapMutex.
956 // Access to individual OutputData::mStream is guarded via its mStreamMutex.
957 // mCancel is atomic, as is mPersistFlags (accessed via MakeOutputStream).
958 // If you end up touching this method and needing other member access, bear
961 nsWebBrowserPersist::OnDataAvailable(nsIRequest
* request
,
962 nsIInputStream
* aIStream
, uint64_t aOffset
,
964 // MOZ_ASSERT(!NS_IsMainThread()); // no guarantees, but it's likely.
966 bool cancel
= mCancel
;
969 uint32_t bytesRemaining
= aLength
;
971 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
972 NS_ENSURE_TRUE(channel
, NS_ERROR_FAILURE
);
974 MutexAutoLock
lock(mOutputMapMutex
);
975 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(request
);
976 OutputData
* data
= mOutputMap
.Get(keyPtr
);
978 // might be uploadData; consume necko's buffer and bail...
980 return aIStream
->ReadSegments(NS_DiscardSegment
, nullptr, aLength
, &n
);
983 bool readError
= true;
985 MutexAutoLock
streamLock(data
->mStreamMutex
);
986 // Make the output stream
987 if (!data
->mStream
) {
988 rv
= MakeOutputStream(data
->mFile
, getter_AddRefs(data
->mStream
));
995 // Read data from the input and write to the output
998 while (!cancel
&& bytesRemaining
) {
1000 rv
= aIStream
->Read(buffer
,
1001 std::min(uint32_t(sizeof(buffer
)), bytesRemaining
),
1003 if (NS_SUCCEEDED(rv
)) {
1005 // Write out the data until something goes wrong, or, it is
1006 // all written. We loop because for some errors (e.g., disk
1007 // full), we get NS_OK with some bytes written, then an error.
1008 // So, we want to write again in that case to get the actual
1010 const char* bufPtr
= buffer
; // Where to write from.
1011 while (NS_SUCCEEDED(rv
) && bytesRead
) {
1012 uint32_t bytesWritten
= 0;
1013 rv
= data
->mStream
->Write(bufPtr
, bytesRead
, &bytesWritten
);
1014 if (NS_SUCCEEDED(rv
)) {
1015 bytesRead
-= bytesWritten
;
1016 bufPtr
+= bytesWritten
;
1017 bytesRemaining
-= bytesWritten
;
1018 // Force an error if (for some reason) we get NS_OK but
1019 // no bytes written.
1020 if (!bytesWritten
) {
1021 rv
= NS_ERROR_FAILURE
;
1025 // Disaster - can't write out the bytes - disk full / permission?
1030 // Disaster - can't read the bytes - broken link / file error?
1035 int64_t channelContentLength
= -1;
1037 NS_SUCCEEDED(channel
->GetContentLength(&channelContentLength
))) {
1038 // if we get -1 at this point, we didn't get content-length header
1039 // assume that we got all of the data and push what we have;
1040 // that's the best we can do now
1041 if ((-1 == channelContentLength
) ||
1042 ((channelContentLength
- (aOffset
+ aLength
)) == 0)) {
1043 NS_WARNING_ASSERTION(
1044 channelContentLength
!= -1,
1045 "nsWebBrowserPersist::OnDataAvailable() no content length "
1046 "header, pushing what we have");
1047 // we're done with this pass; see if we need to do upload
1048 nsAutoCString contentType
;
1049 channel
->GetContentType(contentType
);
1050 // if we don't have the right type of output stream then it's a local
1052 nsCOMPtr
<nsIStorageStream
> storStream(do_QueryInterface(data
->mStream
));
1054 data
->mStream
->Close();
1056 nullptr; // null out stream so we don't close it later
1057 MOZ_ASSERT(NS_IsMainThread(),
1058 "Uploads should be on the main thread.");
1059 rv
= StartUpload(storStream
, data
->mFile
, contentType
);
1060 if (NS_FAILED(rv
)) {
1068 // Notify listener if an error occurred.
1070 RefPtr
<nsIRequest
> req
= readError
? request
: nullptr;
1071 nsCOMPtr
<nsIURI
> file
= data
->mFile
;
1072 RefPtr
<Runnable
> errorOnMainThread
= NS_NewRunnableFunction(
1073 "nsWebBrowserPersist::SendErrorStatusChange",
1074 [self
= RefPtr
{this}, req
, file
, readError
, rv
]() {
1075 self
->SendErrorStatusChange(readError
, rv
, req
, file
);
1077 NS_DispatchToMainThread(errorOnMainThread
);
1079 // And end the download on the main thread.
1080 nsCOMPtr
<nsIRunnable
> endOnMainThread
= NewRunnableMethod
<nsresult
>(
1081 "nsWebBrowserPersist::EndDownload", this,
1082 &nsWebBrowserPersist::EndDownload
, NS_BINDING_ABORTED
);
1083 NS_DispatchToMainThread(endOnMainThread
);
1087 return cancel
? NS_BINDING_ABORTED
: NS_OK
;
1090 //*****************************************************************************
1091 // nsWebBrowserPersist::nsIThreadRetargetableStreamListener
1092 //*****************************************************************************
1094 NS_IMETHODIMP
nsWebBrowserPersist::CheckListenerChain() { return NS_OK
; }
1096 //*****************************************************************************
1097 // nsWebBrowserPersist::nsIProgressEventSink
1098 //*****************************************************************************
1100 NS_IMETHODIMP
nsWebBrowserPersist::OnProgress(nsIRequest
* request
,
1102 int64_t aProgressMax
) {
1103 if (!mProgressListener
) {
1107 // Store the progress of this request
1108 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(request
);
1109 OutputData
* data
= mOutputMap
.Get(keyPtr
);
1111 data
->mSelfProgress
= aProgress
;
1112 data
->mSelfProgressMax
= aProgressMax
;
1114 UploadData
* upData
= mUploadList
.Get(keyPtr
);
1116 upData
->mSelfProgress
= aProgress
;
1117 upData
->mSelfProgressMax
= aProgressMax
;
1121 // Notify listener of total progress
1122 CalcTotalProgress();
1123 if (mProgressListener2
) {
1124 mProgressListener2
->OnProgressChange64(nullptr, request
, aProgress
,
1125 aProgressMax
, mTotalCurrentProgress
,
1128 // have to truncate 64-bit to 32bit
1129 mProgressListener
->OnProgressChange(
1130 nullptr, request
, uint64_t(aProgress
), uint64_t(aProgressMax
),
1131 mTotalCurrentProgress
, mTotalMaxProgress
);
1134 // If our progress listener implements nsIProgressEventSink,
1135 // forward the notification
1137 mEventSink
->OnProgress(request
, aProgress
, aProgressMax
);
1143 NS_IMETHODIMP
nsWebBrowserPersist::OnStatus(nsIRequest
* request
,
1145 const char16_t
* statusArg
) {
1146 if (mProgressListener
) {
1147 // We need to filter out non-error error codes.
1148 // Is the only NS_SUCCEEDED value NS_OK?
1150 case NS_NET_STATUS_RESOLVING_HOST
:
1151 case NS_NET_STATUS_RESOLVED_HOST
:
1152 case NS_NET_STATUS_CONNECTING_TO
:
1153 case NS_NET_STATUS_CONNECTED_TO
:
1154 case NS_NET_STATUS_TLS_HANDSHAKE_STARTING
:
1155 case NS_NET_STATUS_TLS_HANDSHAKE_ENDED
:
1156 case NS_NET_STATUS_SENDING_TO
:
1157 case NS_NET_STATUS_RECEIVING_FROM
:
1158 case NS_NET_STATUS_WAITING_FOR
:
1159 case NS_NET_STATUS_READING
:
1160 case NS_NET_STATUS_WRITING
:
1164 // Pass other notifications (for legitimate errors) along.
1165 mProgressListener
->OnStatusChange(nullptr, request
, status
, statusArg
);
1170 // If our progress listener implements nsIProgressEventSink,
1171 // forward the notification
1173 mEventSink
->OnStatus(request
, status
, statusArg
);
1179 //*****************************************************************************
1180 // nsWebBrowserPersist private methods
1181 //*****************************************************************************
1183 // Convert error info into proper message text and send OnStatusChange
1184 // notification to the web progress listener.
1185 nsresult
nsWebBrowserPersist::SendErrorStatusChange(bool aIsReadError
,
1187 nsIRequest
* aRequest
,
1189 NS_ENSURE_ARG_POINTER(aURI
);
1191 if (!mProgressListener
) {
1196 // Get the file path or spec from the supplied URI
1197 nsCOMPtr
<nsIFile
> file
;
1198 GetLocalFileFromURI(aURI
, getter_AddRefs(file
));
1199 AutoTArray
<nsString
, 1> strings
;
1202 file
->GetPath(*strings
.AppendElement());
1204 nsAutoCString fileurl
;
1205 rv
= aURI
->GetSpec(fileurl
);
1206 NS_ENSURE_SUCCESS(rv
, rv
);
1207 CopyUTF8toUTF16(fileurl
, *strings
.AppendElement());
1212 case NS_ERROR_FILE_NAME_TOO_LONG
:
1213 // File name too long.
1214 msgId
= "fileNameTooLongError";
1216 case NS_ERROR_FILE_ALREADY_EXISTS
:
1217 // File exists with same name as directory.
1218 msgId
= "fileAlreadyExistsError";
1220 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
1221 // Out of space on target volume.
1225 case NS_ERROR_FILE_READ_ONLY
:
1226 // Attempt to write to read/only file.
1230 case NS_ERROR_FILE_ACCESS_DENIED
:
1231 // Attempt to write without sufficient permissions.
1232 msgId
= "accessError";
1236 // Generic read/write error message.
1238 msgId
= "readError";
1240 msgId
= "writeError";
1243 // Get properties file bundle and extract status string.
1244 nsCOMPtr
<nsIStringBundleService
> s
=
1245 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
1246 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && s
, NS_ERROR_FAILURE
);
1248 nsCOMPtr
<nsIStringBundle
> bundle
;
1249 rv
= s
->CreateBundle(kWebBrowserPersistStringBundle
, getter_AddRefs(bundle
));
1250 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && bundle
, NS_ERROR_FAILURE
);
1252 nsAutoString msgText
;
1253 rv
= bundle
->FormatStringFromName(msgId
, strings
, msgText
);
1254 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
1256 mProgressListener
->OnStatusChange(nullptr, aRequest
, aResult
, msgText
.get());
1261 nsresult
nsWebBrowserPersist::GetValidURIFromObject(nsISupports
* aObject
,
1262 nsIURI
** aURI
) const {
1263 NS_ENSURE_ARG_POINTER(aObject
);
1264 NS_ENSURE_ARG_POINTER(aURI
);
1266 nsCOMPtr
<nsIFile
> objAsFile
= do_QueryInterface(aObject
);
1268 return NS_NewFileURI(aURI
, objAsFile
);
1270 nsCOMPtr
<nsIURI
> objAsURI
= do_QueryInterface(aObject
);
1277 return NS_ERROR_FAILURE
;
1281 nsresult
nsWebBrowserPersist::GetLocalFileFromURI(nsIURI
* aURI
,
1282 nsIFile
** aLocalFile
) {
1285 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(aURI
, &rv
);
1286 if (NS_FAILED(rv
)) return rv
;
1288 nsCOMPtr
<nsIFile
> file
;
1289 rv
= fileURL
->GetFile(getter_AddRefs(file
));
1290 if (NS_FAILED(rv
)) {
1294 file
.forget(aLocalFile
);
1299 nsresult
nsWebBrowserPersist::AppendPathToURI(nsIURI
* aURI
,
1300 const nsAString
& aPath
,
1301 nsCOMPtr
<nsIURI
>& aOutURI
) {
1302 NS_ENSURE_ARG_POINTER(aURI
);
1304 nsAutoCString newPath
;
1305 nsresult rv
= aURI
->GetPathQueryRef(newPath
);
1306 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
1308 // Append a forward slash if necessary
1309 int32_t len
= newPath
.Length();
1310 if (len
> 0 && newPath
.CharAt(len
- 1) != '/') {
1311 newPath
.Append('/');
1314 // Store the path back on the URI
1315 AppendUTF16toUTF8(aPath
, newPath
);
1317 return NS_MutateURI(aURI
).SetPathQueryRef(newPath
).Finalize(aOutURI
);
1320 nsresult
nsWebBrowserPersist::SaveURIInternal(
1321 nsIURI
* aURI
, nsIPrincipal
* aTriggeringPrincipal
,
1322 nsContentPolicyType aContentPolicyType
, uint32_t aCacheKey
,
1323 nsIReferrerInfo
* aReferrerInfo
, nsICookieJarSettings
* aCookieJarSettings
,
1324 nsIInputStream
* aPostData
, const char* aExtraHeaders
, nsIURI
* aFile
,
1325 bool aCalcFileExt
, bool aIsPrivate
) {
1326 NS_ENSURE_ARG_POINTER(aURI
);
1327 NS_ENSURE_ARG_POINTER(aFile
);
1328 NS_ENSURE_ARG_POINTER(aTriggeringPrincipal
);
1330 nsresult rv
= NS_OK
;
1334 nsLoadFlags loadFlags
= nsIRequest::LOAD_NORMAL
;
1335 if (mPersistFlags
& PERSIST_FLAGS_BYPASS_CACHE
) {
1336 loadFlags
|= nsIRequest::LOAD_BYPASS_CACHE
;
1337 } else if (mPersistFlags
& PERSIST_FLAGS_FROM_CACHE
) {
1338 loadFlags
|= nsIRequest::LOAD_FROM_CACHE
;
1341 // If there is no cookieJarSetting given, we need to create a new
1342 // cookieJarSettings for this download in order to send cookies based on the
1343 // current state of the prefs/permissions.
1344 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
= aCookieJarSettings
;
1345 if (!cookieJarSettings
) {
1348 ? net::CookieJarSettings::Create(net::CookieJarSettings::ePrivate
)
1349 : net::CookieJarSettings::Create(net::CookieJarSettings::eRegular
);
1352 // Open a channel to the URI
1353 nsCOMPtr
<nsIChannel
> inputChannel
;
1354 rv
= NS_NewChannel(getter_AddRefs(inputChannel
), aURI
, aTriggeringPrincipal
,
1355 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
1356 aContentPolicyType
, cookieJarSettings
,
1357 nullptr, // aPerformanceStorage
1358 nullptr, // aLoadGroup
1359 static_cast<nsIInterfaceRequestor
*>(this), loadFlags
);
1361 nsCOMPtr
<nsIPrivateBrowsingChannel
> pbChannel
=
1362 do_QueryInterface(inputChannel
);
1364 pbChannel
->SetPrivate(aIsPrivate
);
1367 if (NS_FAILED(rv
) || inputChannel
== nullptr) {
1368 EndDownload(NS_ERROR_FAILURE
);
1369 return NS_ERROR_FAILURE
;
1372 // Disable content conversion
1373 if (mPersistFlags
& PERSIST_FLAGS_NO_CONVERSION
) {
1374 nsCOMPtr
<nsIEncodedChannel
> encodedChannel(do_QueryInterface(inputChannel
));
1375 if (encodedChannel
) {
1376 encodedChannel
->SetApplyConversion(false);
1380 // Set the referrer, post data and headers if any
1381 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(inputChannel
));
1383 if (aReferrerInfo
) {
1384 DebugOnly
<nsresult
> success
= httpChannel
->SetReferrerInfo(aReferrerInfo
);
1385 MOZ_ASSERT(NS_SUCCEEDED(success
));
1390 nsCOMPtr
<nsISeekableStream
> stream(do_QueryInterface(aPostData
));
1392 // Rewind the postdata stream
1393 stream
->Seek(nsISeekableStream::NS_SEEK_SET
, 0);
1394 nsCOMPtr
<nsIUploadChannel
> uploadChannel(
1395 do_QueryInterface(httpChannel
));
1396 NS_ASSERTION(uploadChannel
, "http must support nsIUploadChannel");
1397 // Attach the postdata to the http channel
1398 uploadChannel
->SetUploadStream(aPostData
, ""_ns
, -1);
1403 nsCOMPtr
<nsICacheInfoChannel
> cacheChannel(do_QueryInterface(httpChannel
));
1404 if (cacheChannel
&& aCacheKey
!= 0) {
1405 cacheChannel
->SetCacheKey(aCacheKey
);
1409 if (aExtraHeaders
) {
1410 nsAutoCString oneHeader
;
1411 nsAutoCString headerName
;
1412 nsAutoCString headerValue
;
1415 const char* kWhitespace
= "\b\t\r\n ";
1416 nsAutoCString
extraHeaders(aExtraHeaders
);
1418 crlf
= extraHeaders
.Find("\r\n", true);
1419 if (crlf
== -1) break;
1420 extraHeaders
.Mid(oneHeader
, 0, crlf
);
1421 extraHeaders
.Cut(0, crlf
+ 2);
1422 colon
= oneHeader
.Find(":");
1423 if (colon
== -1) break; // Should have a colon
1424 oneHeader
.Left(headerName
, colon
);
1426 oneHeader
.Mid(headerValue
, colon
, oneHeader
.Length() - colon
);
1427 headerName
.Trim(kWhitespace
);
1428 headerValue
.Trim(kWhitespace
);
1429 // Add the header (merging if required)
1430 rv
= httpChannel
->SetRequestHeader(headerName
, headerValue
, true);
1431 if (NS_FAILED(rv
)) {
1432 EndDownload(NS_ERROR_FAILURE
);
1433 return NS_ERROR_FAILURE
;
1438 return SaveChannelInternal(inputChannel
, aFile
, aCalcFileExt
);
1441 nsresult
nsWebBrowserPersist::SaveChannelInternal(nsIChannel
* aChannel
,
1443 bool aCalcFileExt
) {
1444 NS_ENSURE_ARG_POINTER(aChannel
);
1445 NS_ENSURE_ARG_POINTER(aFile
);
1447 // The default behaviour of SaveChannelInternal is to download the source
1448 // into a storage stream and upload that to the target. MakeOutputStream
1449 // special-cases a file target and creates a file output stream directly.
1450 // We want to special-case a file source and create a file input stream,
1451 // but we don't need to do this in the case of a file target.
1452 nsCOMPtr
<nsIFileChannel
> fc(do_QueryInterface(aChannel
));
1453 nsCOMPtr
<nsIFileURL
> fu(do_QueryInterface(aFile
));
1456 nsCOMPtr
<nsIInputStream
> fileInputStream
, bufferedInputStream
;
1458 NS_MaybeOpenChannelUsingOpen(aChannel
, getter_AddRefs(fileInputStream
));
1459 NS_ENSURE_SUCCESS(rv
, rv
);
1460 rv
= NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream
),
1461 fileInputStream
.forget(),
1462 BUFFERED_OUTPUT_SIZE
);
1463 NS_ENSURE_SUCCESS(rv
, rv
);
1464 nsAutoCString contentType
;
1465 aChannel
->GetContentType(contentType
);
1466 return StartUpload(bufferedInputStream
, aFile
, contentType
);
1469 // Mark save channel as throttleable.
1470 nsCOMPtr
<nsIClassOfService
> cos(do_QueryInterface(aChannel
));
1472 cos
->AddClassFlags(nsIClassOfService::Throttleable
);
1475 // Read from the input channel
1476 nsresult rv
= NS_MaybeOpenChannelUsingAsyncOpen(aChannel
, this);
1477 if (rv
== NS_ERROR_NO_CONTENT
) {
1478 // Assume this is a protocol such as mailto: which does not feed out
1479 // data and just ignore it.
1480 return NS_SUCCESS_DONT_FIXUP
;
1483 if (NS_FAILED(rv
)) {
1484 // Opening failed, but do we care?
1485 if (mPersistFlags
& PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS
) {
1486 SendErrorStatusChange(true, rv
, aChannel
, aFile
);
1487 EndDownload(NS_ERROR_FAILURE
);
1488 return NS_ERROR_FAILURE
;
1490 return NS_SUCCESS_DONT_FIXUP
;
1493 MutexAutoLock
lock(mOutputMapMutex
);
1494 // Add the output transport to the output map with the channel as the key
1495 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(aChannel
);
1496 mOutputMap
.InsertOrUpdate(keyPtr
,
1497 MakeUnique
<OutputData
>(aFile
, mURI
, aCalcFileExt
));
1502 nsresult
nsWebBrowserPersist::GetExtensionForContentType(
1503 const char16_t
* aContentType
, char16_t
** aExt
) {
1504 NS_ENSURE_ARG_POINTER(aContentType
);
1505 NS_ENSURE_ARG_POINTER(aExt
);
1510 if (!mMIMEService
) {
1511 mMIMEService
= do_GetService(NS_MIMESERVICE_CONTRACTID
, &rv
);
1512 NS_ENSURE_TRUE(mMIMEService
, NS_ERROR_FAILURE
);
1515 nsAutoCString contentType
;
1516 LossyCopyUTF16toASCII(MakeStringSpan(aContentType
), contentType
);
1518 rv
= mMIMEService
->GetPrimaryExtension(contentType
, ""_ns
, ext
);
1519 if (NS_SUCCEEDED(rv
)) {
1520 *aExt
= UTF8ToNewUnicode(ext
);
1521 NS_ENSURE_TRUE(*aExt
, NS_ERROR_OUT_OF_MEMORY
);
1525 return NS_ERROR_FAILURE
;
1528 nsresult
nsWebBrowserPersist::SaveDocumentDeferred(
1529 mozilla::UniquePtr
<WalkData
>&& aData
) {
1531 SaveDocumentInternal(aData
->mDocument
, aData
->mFile
, aData
->mDataPath
);
1532 if (NS_FAILED(rv
)) {
1533 SendErrorStatusChange(true, rv
, nullptr, mURI
);
1539 nsresult
nsWebBrowserPersist::SaveDocumentInternal(
1540 nsIWebBrowserPersistDocument
* aDocument
, nsIURI
* aFile
, nsIURI
* aDataPath
) {
1542 NS_ENSURE_ARG_POINTER(aDocument
);
1543 NS_ENSURE_ARG_POINTER(aFile
);
1545 nsresult rv
= aDocument
->SetPersistFlags(mPersistFlags
);
1546 NS_ENSURE_SUCCESS(rv
, rv
);
1548 rv
= aDocument
->GetIsPrivate(&mIsPrivate
);
1549 NS_ENSURE_SUCCESS(rv
, rv
);
1551 // See if we can get the local file representation of this URI
1552 nsCOMPtr
<nsIFile
> localFile
;
1553 rv
= GetLocalFileFromURI(aFile
, getter_AddRefs(localFile
));
1555 nsCOMPtr
<nsIFile
> localDataPath
;
1556 if (NS_SUCCEEDED(rv
) && aDataPath
) {
1557 // See if we can get the local file representation of this URI
1558 rv
= GetLocalFileFromURI(aDataPath
, getter_AddRefs(localDataPath
));
1559 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
1562 // Persist the main document
1563 rv
= aDocument
->GetCharacterSet(mCurrentCharset
);
1564 NS_ENSURE_SUCCESS(rv
, rv
);
1565 nsAutoCString uriSpec
;
1566 rv
= aDocument
->GetDocumentURI(uriSpec
);
1567 NS_ENSURE_SUCCESS(rv
, rv
);
1568 rv
= NS_NewURI(getter_AddRefs(mURI
), uriSpec
, mCurrentCharset
.get());
1569 NS_ENSURE_SUCCESS(rv
, rv
);
1570 rv
= aDocument
->GetBaseURI(uriSpec
);
1571 NS_ENSURE_SUCCESS(rv
, rv
);
1572 rv
= NS_NewURI(getter_AddRefs(mCurrentBaseURI
), uriSpec
,
1573 mCurrentCharset
.get());
1574 NS_ENSURE_SUCCESS(rv
, rv
);
1576 // Does the caller want to fixup the referenced URIs and save those too?
1578 // Basic steps are these.
1580 // 1. Iterate through the document (and subdocuments) building a list
1582 // 2. For each URI create an OutputData entry and open a channel to save
1583 // it. As each URI is saved, discover the mime type and fix up the
1584 // local filename with the correct extension.
1585 // 3. Store the document in a list and wait for URI persistence to finish
1586 // 4. After URI persistence completes save the list of documents,
1587 // fixing it up as it goes out to file.
1589 mCurrentDataPathIsRelative
= false;
1590 mCurrentDataPath
= aDataPath
;
1591 mCurrentRelativePathToData
= "";
1592 mCurrentThingsToPersist
= 0;
1593 mTargetBaseURI
= aFile
;
1595 // Determine if the specified data path is relative to the
1596 // specified file, (e.g. c:\docs\htmldata is relative to
1597 // c:\docs\myfile.htm, but not to d:\foo\data.
1599 // Starting with the data dir work back through its parents
1600 // checking if one of them matches the base directory.
1602 if (localDataPath
&& localFile
) {
1603 nsCOMPtr
<nsIFile
> baseDir
;
1604 localFile
->GetParent(getter_AddRefs(baseDir
));
1606 nsAutoCString relativePathToData
;
1607 nsCOMPtr
<nsIFile
> dataDirParent
;
1608 dataDirParent
= localDataPath
;
1609 while (dataDirParent
) {
1610 bool sameDir
= false;
1611 dataDirParent
->Equals(baseDir
, &sameDir
);
1613 mCurrentRelativePathToData
= relativePathToData
;
1614 mCurrentDataPathIsRelative
= true;
1618 nsAutoString dirName
;
1619 dataDirParent
->GetLeafName(dirName
);
1621 nsAutoCString newRelativePathToData
;
1622 newRelativePathToData
=
1623 NS_ConvertUTF16toUTF8(dirName
) + "/"_ns
+ relativePathToData
;
1624 relativePathToData
= newRelativePathToData
;
1626 nsCOMPtr
<nsIFile
> newDataDirParent
;
1627 rv
= dataDirParent
->GetParent(getter_AddRefs(newDataDirParent
));
1628 dataDirParent
= newDataDirParent
;
1631 // generate a relative path if possible
1632 nsCOMPtr
<nsIURL
> pathToBaseURL(do_QueryInterface(aFile
));
1633 if (pathToBaseURL
) {
1634 nsAutoCString relativePath
; // nsACString
1636 pathToBaseURL
->GetRelativeSpec(aDataPath
, relativePath
))) {
1637 mCurrentDataPathIsRelative
= true;
1638 mCurrentRelativePathToData
= relativePath
;
1643 // Store the document in a list so when URI persistence is done and the
1644 // filenames of saved URIs are known, the documents can be fixed up and
1647 auto* docData
= new DocData
;
1648 docData
->mBaseURI
= mCurrentBaseURI
;
1649 docData
->mCharset
= mCurrentCharset
;
1650 docData
->mDocument
= aDocument
;
1651 docData
->mFile
= aFile
;
1652 mDocList
.AppendElement(docData
);
1654 // Walk the DOM gathering a list of externally referenced URIs in the uri
1656 nsCOMPtr
<nsIWebBrowserPersistResourceVisitor
> visit
=
1657 new OnWalk(this, aFile
, localDataPath
);
1658 return aDocument
->ReadResources(visit
);
1660 auto* docData
= new DocData
;
1661 docData
->mBaseURI
= mCurrentBaseURI
;
1662 docData
->mCharset
= mCurrentCharset
;
1663 docData
->mDocument
= aDocument
;
1664 docData
->mFile
= aFile
;
1665 mDocList
.AppendElement(docData
);
1667 // Not walking DOMs, so go directly to serialization.
1668 SerializeNextFile();
1674 nsWebBrowserPersist::OnWalk::VisitResource(
1675 nsIWebBrowserPersistDocument
* aDoc
, const nsACString
& aURI
,
1676 nsContentPolicyType aContentPolicyType
) {
1677 return mParent
->StoreURI(aURI
, aDoc
, aContentPolicyType
);
1681 nsWebBrowserPersist::OnWalk::VisitDocument(
1682 nsIWebBrowserPersistDocument
* aDoc
, nsIWebBrowserPersistDocument
* aSubDoc
) {
1683 URIData
* data
= nullptr;
1684 nsAutoCString uriSpec
;
1685 nsresult rv
= aSubDoc
->GetDocumentURI(uriSpec
);
1686 NS_ENSURE_SUCCESS(rv
, rv
);
1687 rv
= mParent
->StoreURI(uriSpec
, aDoc
, nsIContentPolicy::TYPE_SUBDOCUMENT
,
1689 NS_ENSURE_SUCCESS(rv
, rv
);
1691 // If the URI scheme isn't persistable, then don't persist.
1694 data
->mIsSubFrame
= true;
1695 return mParent
->SaveSubframeContent(aSubDoc
, aDoc
, uriSpec
, data
);
1699 nsWebBrowserPersist::OnWalk::VisitBrowsingContext(
1700 nsIWebBrowserPersistDocument
* aDoc
, BrowsingContext
* aContext
) {
1701 RefPtr
<dom::CanonicalBrowsingContext
> context
= aContext
->Canonical();
1703 if (NS_WARN_IF(!context
->GetCurrentWindowGlobal())) {
1704 EndVisit(nullptr, NS_ERROR_FAILURE
);
1705 return NS_ERROR_FAILURE
;
1708 UniquePtr
<WebBrowserPersistDocumentParent
> actor(
1709 new WebBrowserPersistDocumentParent());
1711 nsCOMPtr
<nsIWebBrowserPersistDocumentReceiver
> receiver
=
1712 new OnRemoteWalk(this, aDoc
);
1713 actor
->SetOnReady(receiver
);
1715 RefPtr
<dom::BrowserParent
> browserParent
=
1716 context
->GetCurrentWindowGlobal()->GetBrowserParent();
1719 context
->GetContentParent()->SendPWebBrowserPersistDocumentConstructor(
1720 actor
.release(), browserParent
, context
);
1722 if (NS_WARN_IF(!ok
)) {
1723 // (The actor will be destroyed on constructor failure.)
1724 EndVisit(nullptr, NS_ERROR_FAILURE
);
1725 return NS_ERROR_FAILURE
;
1728 ++mPendingDocuments
;
1734 nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument
* aDoc
,
1736 if (NS_FAILED(mStatus
)) {
1740 if (NS_FAILED(aStatus
)) {
1742 mParent
->SendErrorStatusChange(true, aStatus
, nullptr, mFile
);
1743 mParent
->EndDownload(aStatus
);
1747 if (--mPendingDocuments
) {
1748 // We're not done yet, wait for more.
1752 mParent
->FinishSaveDocumentInternal(mFile
, mDataPath
);
1757 nsWebBrowserPersist::OnRemoteWalk::OnDocumentReady(
1758 nsIWebBrowserPersistDocument
* aSubDocument
) {
1759 mVisitor
->VisitDocument(mDocument
, aSubDocument
);
1760 mVisitor
->EndVisit(mDocument
, NS_OK
);
1765 nsWebBrowserPersist::OnRemoteWalk::OnError(nsresult aFailure
) {
1766 mVisitor
->EndVisit(nullptr, aFailure
);
1770 void nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI
* aFile
,
1771 nsIFile
* aDataPath
) {
1772 // If there are things to persist, create a directory to hold them
1773 if (mCurrentThingsToPersist
> 0) {
1775 bool exists
= false;
1776 bool haveDir
= false;
1778 aDataPath
->Exists(&exists
);
1780 aDataPath
->IsDirectory(&haveDir
);
1783 nsresult rv
= aDataPath
->Create(nsIFile::DIRECTORY_TYPE
, 0755);
1784 if (NS_SUCCEEDED(rv
)) {
1787 SendErrorStatusChange(false, rv
, nullptr, aFile
);
1791 EndDownload(NS_ERROR_FAILURE
);
1794 if (mPersistFlags
& PERSIST_FLAGS_CLEANUP_ON_FAILURE
) {
1795 // Add to list of things to delete later if all goes wrong
1796 auto* cleanupData
= new CleanupData
;
1797 cleanupData
->mFile
= aDataPath
;
1798 cleanupData
->mIsDirectory
= true;
1799 mCleanupList
.AppendElement(cleanupData
);
1804 if (mWalkStack
.Length() > 0) {
1805 mozilla::UniquePtr
<WalkData
> toWalk
= mWalkStack
.PopLastElement();
1806 // Bounce this off the event loop to avoid stack overflow.
1807 using WalkStorage
= StoreCopyPassByRRef
<decltype(toWalk
)>;
1808 auto saveMethod
= &nsWebBrowserPersist::SaveDocumentDeferred
;
1809 nsCOMPtr
<nsIRunnable
> saveLater
= NewRunnableMethod
<WalkStorage
>(
1810 "nsWebBrowserPersist::FinishSaveDocumentInternal", this, saveMethod
,
1812 NS_DispatchToCurrentThread(saveLater
);
1814 // Done walking DOMs; on to the serialization phase.
1815 SerializeNextFile();
1819 void nsWebBrowserPersist::Cleanup() {
1821 nsClassHashtable
<nsISupportsHashKey
, OutputData
> outputMapCopy
;
1823 MutexAutoLock
lock(mOutputMapMutex
);
1824 mOutputMap
.SwapElements(outputMapCopy
);
1826 for (const auto& key
: outputMapCopy
.Keys()) {
1827 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(key
);
1829 channel
->Cancel(NS_BINDING_ABORTED
);
1832 outputMapCopy
.Clear();
1834 for (const auto& key
: mUploadList
.Keys()) {
1835 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(key
);
1837 channel
->Cancel(NS_BINDING_ABORTED
);
1840 mUploadList
.Clear();
1843 for (i
= 0; i
< mDocList
.Length(); i
++) {
1844 DocData
* docData
= mDocList
.ElementAt(i
);
1849 for (i
= 0; i
< mCleanupList
.Length(); i
++) {
1850 CleanupData
* cleanupData
= mCleanupList
.ElementAt(i
);
1853 mCleanupList
.Clear();
1855 mFilenameList
.Clear();
1858 void nsWebBrowserPersist::CleanupLocalFiles() {
1859 // Two passes, the first pass cleans up files, the second pass tests
1860 // for and then deletes empty directories. Directories that are not
1861 // empty after the first pass must contain files from something else
1862 // and are not deleted.
1864 for (pass
= 0; pass
< 2; pass
++) {
1866 for (i
= 0; i
< mCleanupList
.Length(); i
++) {
1867 CleanupData
* cleanupData
= mCleanupList
.ElementAt(i
);
1868 nsCOMPtr
<nsIFile
> file
= cleanupData
->mFile
;
1870 // Test if the dir / file exists (something in an earlier loop
1871 // may have already removed it)
1872 bool exists
= false;
1873 file
->Exists(&exists
);
1874 if (!exists
) continue;
1876 // Test if the file has changed in between creation and deletion
1877 // in some way that means it should be ignored
1878 bool isDirectory
= false;
1879 file
->IsDirectory(&isDirectory
);
1880 if (isDirectory
!= cleanupData
->mIsDirectory
)
1881 continue; // A file has become a dir or vice versa !
1883 if (pass
== 0 && !isDirectory
) {
1884 file
->Remove(false);
1885 } else if (pass
== 1 && isDirectory
) // Directory
1887 // Directories are more complicated. Enumerate through
1888 // children looking for files. Any files created by the
1889 // persist object would have been deleted by the first
1890 // pass so if there are any there at this stage, the dir
1891 // cannot be deleted because it has someone else's files
1892 // in it. Empty child dirs are deleted but they must be
1893 // recursed through to ensure they are actually empty.
1895 bool isEmptyDirectory
= true;
1896 nsCOMArray
<nsIDirectoryEnumerator
> dirStack
;
1897 int32_t stackSize
= 0;
1899 // Push the top level enum onto the stack
1900 nsCOMPtr
<nsIDirectoryEnumerator
> pos
;
1901 if (NS_SUCCEEDED(file
->GetDirectoryEntries(getter_AddRefs(pos
))))
1902 dirStack
.AppendObject(pos
);
1904 while (isEmptyDirectory
&& (stackSize
= dirStack
.Count())) {
1905 // Pop the last element
1906 nsCOMPtr
<nsIDirectoryEnumerator
> curPos
;
1907 curPos
= dirStack
[stackSize
- 1];
1908 dirStack
.RemoveObjectAt(stackSize
- 1);
1910 nsCOMPtr
<nsIFile
> child
;
1911 if (NS_FAILED(curPos
->GetNextFile(getter_AddRefs(child
))) || !child
) {
1915 bool childIsSymlink
= false;
1916 child
->IsSymlink(&childIsSymlink
);
1917 bool childIsDir
= false;
1918 child
->IsDirectory(&childIsDir
);
1919 if (!childIsDir
|| childIsSymlink
) {
1920 // Some kind of file or symlink which means dir
1921 // is not empty so just drop out.
1922 isEmptyDirectory
= false;
1925 // Push parent enumerator followed by child enumerator
1926 nsCOMPtr
<nsIDirectoryEnumerator
> childPos
;
1927 child
->GetDirectoryEntries(getter_AddRefs(childPos
));
1928 dirStack
.AppendObject(curPos
);
1929 if (childPos
) dirStack
.AppendObject(childPos
);
1933 // If after all that walking the dir is deemed empty, delete it
1934 if (isEmptyDirectory
) {
1942 nsresult
nsWebBrowserPersist::CalculateUniqueFilename(
1943 nsIURI
* aURI
, nsCOMPtr
<nsIURI
>& aOutURI
) {
1944 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aURI
));
1945 NS_ENSURE_TRUE(url
, NS_ERROR_FAILURE
);
1947 bool nameHasChanged
= false;
1950 // Get the old filename
1951 nsAutoCString filename
;
1952 rv
= url
->GetFileName(filename
);
1953 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
1954 nsAutoCString directory
;
1955 rv
= url
->GetDirectory(directory
);
1956 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
1958 // Split the filename into a base and an extension.
1959 // e.g. "foo.html" becomes "foo" & ".html"
1961 // The nsIURL methods GetFileBaseName & GetFileExtension don't
1962 // preserve the dot whereas this code does to save some effort
1963 // later when everything is put back together.
1964 int32_t lastDot
= filename
.RFind(".");
1968 filename
.Mid(base
, 0, lastDot
);
1969 filename
.Mid(ext
, lastDot
, filename
.Length() - lastDot
); // includes dot
1971 // filename contains no dot
1975 // Test if the filename is longer than allowed by the OS
1976 int32_t needToChop
= filename
.Length() - kDefaultMaxFilenameLength
;
1977 if (needToChop
> 0) {
1978 // Truncate the base first and then the ext if necessary
1979 if (base
.Length() > (uint32_t)needToChop
) {
1980 base
.Truncate(base
.Length() - needToChop
);
1982 needToChop
-= base
.Length() - 1;
1984 if (ext
.Length() > (uint32_t)needToChop
) {
1985 ext
.Truncate(ext
.Length() - needToChop
);
1989 // If kDefaultMaxFilenameLength were 1 we'd be in trouble here,
1990 // but that won't happen because it will be set to a sensible
1994 filename
.Assign(base
);
1995 filename
.Append(ext
);
1996 nameHasChanged
= true;
1999 // Ensure the filename is unique
2000 // Create a filename if it's empty, or if the filename / datapath is
2001 // already taken by another URI and create an alternate name.
2003 if (base
.IsEmpty() || !mFilenameList
.IsEmpty()) {
2004 nsAutoCString tmpPath
;
2005 nsAutoCString tmpBase
;
2006 uint32_t duplicateCounter
= 1;
2008 // Make a file name,
2009 // Foo become foo_001, foo_002, etc.
2010 // Empty files become _001, _002 etc.
2012 if (base
.IsEmpty() || duplicateCounter
> 1) {
2013 SmprintfPointer tmp
= mozilla::Smprintf("_%03d", duplicateCounter
);
2014 NS_ENSURE_TRUE(tmp
, NS_ERROR_OUT_OF_MEMORY
);
2015 if (filename
.Length() < kDefaultMaxFilenameLength
- 4) {
2018 base
.Mid(tmpBase
, 0, base
.Length() - 4);
2020 tmpBase
.Append(tmp
.get());
2025 tmpPath
.Assign(directory
);
2026 tmpPath
.Append(tmpBase
);
2027 tmpPath
.Append(ext
);
2029 // Test if the name is a duplicate
2030 if (!mFilenameList
.Contains(tmpPath
)) {
2031 if (!base
.Equals(tmpBase
)) {
2032 filename
.Assign(tmpBase
);
2033 filename
.Append(ext
);
2034 nameHasChanged
= true;
2042 // Add name to list of those already used
2043 nsAutoCString
newFilepath(directory
);
2044 newFilepath
.Append(filename
);
2045 mFilenameList
.AppendElement(newFilepath
);
2047 // Update the uri accordingly if the filename actually changed
2048 if (nameHasChanged
) {
2049 // Final sanity test
2050 if (filename
.Length() > kDefaultMaxFilenameLength
) {
2052 "Filename wasn't truncated less than the max file length - how can "
2054 return NS_ERROR_FAILURE
;
2057 nsCOMPtr
<nsIFile
> localFile
;
2058 GetLocalFileFromURI(aURI
, getter_AddRefs(localFile
));
2061 nsAutoString filenameAsUnichar
;
2062 CopyASCIItoUTF16(filename
, filenameAsUnichar
);
2063 localFile
->SetLeafName(filenameAsUnichar
);
2065 // Resync the URI with the file after the extension has been appended
2066 return NS_MutateURI(aURI
)
2067 .Apply(&nsIFileURLMutator::SetFile
, localFile
)
2070 return NS_MutateURI(url
)
2071 .Apply(&nsIURLMutator::SetFileName
, filename
, nullptr)
2075 // TODO (:valentin) This method should always clone aURI
2080 nsresult
nsWebBrowserPersist::MakeFilenameFromURI(nsIURI
* aURI
,
2081 nsString
& aFilename
) {
2082 // Try to get filename from the URI.
2083 nsAutoString fileName
;
2085 // Get a suggested file name from the URL but strip it of characters
2086 // likely to cause the name to be illegal.
2088 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aURI
));
2090 nsAutoCString nameFromURL
;
2091 url
->GetFileName(nameFromURL
);
2092 if (mPersistFlags
& PERSIST_FLAGS_DONT_CHANGE_FILENAMES
) {
2093 CopyASCIItoUTF16(NS_UnescapeURL(nameFromURL
), fileName
);
2094 aFilename
= fileName
;
2097 if (!nameFromURL
.IsEmpty()) {
2098 // Unescape the file name (GetFileName escapes it)
2099 NS_UnescapeURL(nameFromURL
);
2100 uint32_t nameLength
= 0;
2101 const char* p
= nameFromURL
.get();
2102 for (; *p
&& *p
!= ';' && *p
!= '?' && *p
!= '#' && *p
!= '.'; p
++) {
2103 if (IsAsciiAlpha(*p
) || IsAsciiDigit(*p
) || *p
== '.' || *p
== '-' ||
2104 *p
== '_' || (*p
== ' ')) {
2105 fileName
.Append(char16_t(*p
));
2106 if (++nameLength
== kDefaultMaxFilenameLength
) {
2108 // There is no point going any further since it will be
2109 // truncated in CalculateUniqueFilename anyway.
2110 // More importantly, certain implementations of
2111 // nsIFile (e.g. the Mac impl) might truncate
2112 // names in undesirable ways, such as truncating from
2113 // the middle, inserting ellipsis and so on.
2121 // Empty filenames can confuse the local file object later
2122 // when it attempts to set the leaf name in CalculateUniqueFilename
2123 // for duplicates and ends up replacing the parent dir. To avoid
2124 // the problem, all filenames are made at least one character long.
2125 if (fileName
.IsEmpty()) {
2126 fileName
.Append(char16_t('a')); // 'a' is for arbitrary
2129 aFilename
= fileName
;
2133 nsresult
nsWebBrowserPersist::CalculateAndAppendFileExt(
2134 nsIURI
* aURI
, nsIChannel
* aChannel
, nsIURI
* aOriginalURIWithExtension
,
2135 nsCOMPtr
<nsIURI
>& aOutURI
) {
2136 nsresult rv
= NS_OK
;
2138 if (!mMIMEService
) {
2139 mMIMEService
= do_GetService(NS_MIMESERVICE_CONTRACTID
, &rv
);
2140 NS_ENSURE_TRUE(mMIMEService
, NS_ERROR_FAILURE
);
2143 nsAutoCString contentType
;
2145 // Get the content type from the channel
2146 aChannel
->GetContentType(contentType
);
2148 // Get the content type from the MIME service
2149 if (contentType
.IsEmpty()) {
2150 nsCOMPtr
<nsIURI
> uri
;
2151 aChannel
->GetOriginalURI(getter_AddRefs(uri
));
2152 mMIMEService
->GetTypeFromURI(uri
, contentType
);
2155 // Append the extension onto the file
2156 if (!contentType
.IsEmpty()) {
2157 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
2158 mMIMEService
->GetFromTypeAndExtension(contentType
, ""_ns
,
2159 getter_AddRefs(mimeInfo
));
2161 nsCOMPtr
<nsIFile
> localFile
;
2162 GetLocalFileFromURI(aURI
, getter_AddRefs(localFile
));
2165 nsCOMPtr
<nsIURL
> url(do_QueryInterface(aURI
));
2166 NS_ENSURE_TRUE(url
, NS_ERROR_FAILURE
);
2168 nsAutoCString newFileName
;
2169 url
->GetFileName(newFileName
);
2171 // Test if the current extension is current for the mime type
2172 bool hasExtension
= false;
2173 int32_t ext
= newFileName
.RFind(".");
2175 mimeInfo
->ExtensionExists(Substring(newFileName
, ext
+ 1),
2179 // Append the mime file extension
2180 nsAutoCString fileExt
;
2181 if (!hasExtension
) {
2182 // Test if previous extension is acceptable
2183 nsCOMPtr
<nsIURL
> oldurl(do_QueryInterface(aOriginalURIWithExtension
));
2184 NS_ENSURE_TRUE(oldurl
, NS_ERROR_FAILURE
);
2185 oldurl
->GetFileExtension(fileExt
);
2186 bool useOldExt
= false;
2187 if (!fileExt
.IsEmpty()) {
2188 mimeInfo
->ExtensionExists(fileExt
, &useOldExt
);
2191 // If the url doesn't have an extension, or we don't know the extension,
2192 // try to use the primary extension for the type. If we don't know the
2193 // primary extension for the type, just continue with the url extension.
2195 nsAutoCString primaryExt
;
2196 mimeInfo
->GetPrimaryExtension(primaryExt
);
2197 if (!primaryExt
.IsEmpty()) {
2198 fileExt
= primaryExt
;
2202 if (!fileExt
.IsEmpty()) {
2203 uint32_t newLength
= newFileName
.Length() + fileExt
.Length() + 1;
2204 if (newLength
> kDefaultMaxFilenameLength
) {
2205 if (fileExt
.Length() > kDefaultMaxFilenameLength
/ 2)
2206 fileExt
.Truncate(kDefaultMaxFilenameLength
/ 2);
2208 uint32_t diff
= kDefaultMaxFilenameLength
- 1 - fileExt
.Length();
2209 if (newFileName
.Length() > diff
) newFileName
.Truncate(diff
);
2211 newFileName
.Append('.');
2212 newFileName
.Append(fileExt
);
2216 localFile
->SetLeafName(NS_ConvertUTF8toUTF16(newFileName
));
2218 // Resync the URI with the file after the extension has been appended
2219 return NS_MutateURI(url
)
2220 .Apply(&nsIFileURLMutator::SetFile
, localFile
)
2223 return NS_MutateURI(url
)
2224 .Apply(&nsIURLMutator::SetFileName
, newFileName
, nullptr)
2230 // TODO (:valentin) This method should always clone aURI
2235 // Note: the MakeOutputStream helpers can be called from a background thread.
2236 nsresult
nsWebBrowserPersist::MakeOutputStream(
2237 nsIURI
* aURI
, nsIOutputStream
** aOutputStream
) {
2240 nsCOMPtr
<nsIFile
> localFile
;
2241 GetLocalFileFromURI(aURI
, getter_AddRefs(localFile
));
2243 rv
= MakeOutputStreamFromFile(localFile
, aOutputStream
);
2245 rv
= MakeOutputStreamFromURI(aURI
, aOutputStream
);
2250 nsresult
nsWebBrowserPersist::MakeOutputStreamFromFile(
2251 nsIFile
* aFile
, nsIOutputStream
** aOutputStream
) {
2252 nsresult rv
= NS_OK
;
2254 nsCOMPtr
<nsIFileOutputStream
> fileOutputStream
=
2255 do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID
, &rv
);
2256 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
2258 // XXX brade: get the right flags here!
2259 int32_t ioFlags
= -1;
2260 if (mPersistFlags
& nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE
)
2261 ioFlags
= PR_APPEND
| PR_CREATE_FILE
| PR_WRONLY
;
2262 rv
= fileOutputStream
->Init(aFile
, ioFlags
, -1, 0);
2263 NS_ENSURE_SUCCESS(rv
, rv
);
2265 rv
= NS_NewBufferedOutputStream(aOutputStream
, fileOutputStream
.forget(),
2266 BUFFERED_OUTPUT_SIZE
);
2267 NS_ENSURE_SUCCESS(rv
, rv
);
2269 if (mPersistFlags
& PERSIST_FLAGS_CLEANUP_ON_FAILURE
) {
2270 // Add to cleanup list in event of failure
2271 auto* cleanupData
= new CleanupData
;
2272 cleanupData
->mFile
= aFile
;
2273 cleanupData
->mIsDirectory
= false;
2274 if (NS_IsMainThread()) {
2275 mCleanupList
.AppendElement(cleanupData
);
2277 // If we're on a background thread, add the cleanup back on the main
2279 RefPtr
<Runnable
> addCleanup
= NS_NewRunnableFunction(
2280 "nsWebBrowserPersist::AddCleanupToList",
2281 [self
= RefPtr
{this}, cleanup
= std::move(cleanupData
)]() {
2282 self
->mCleanupList
.AppendElement(cleanup
);
2284 NS_DispatchToMainThread(addCleanup
);
2291 nsresult
nsWebBrowserPersist::MakeOutputStreamFromURI(
2292 nsIURI
* aURI
, nsIOutputStream
** aOutputStream
) {
2293 uint32_t segsize
= 8192;
2294 uint32_t maxsize
= uint32_t(-1);
2295 nsCOMPtr
<nsIStorageStream
> storStream
;
2297 NS_NewStorageStream(segsize
, maxsize
, getter_AddRefs(storStream
));
2298 NS_ENSURE_SUCCESS(rv
, rv
);
2300 NS_ENSURE_SUCCESS(CallQueryInterface(storStream
, aOutputStream
),
2305 void nsWebBrowserPersist::FinishDownload() {
2306 // We call FinishDownload when we run out of things to download for this
2307 // persist operation, by dispatching this method to the main thread. By now,
2308 // it's possible that we have been canceled or encountered an error earlier
2309 // in the download, or something else called EndDownload. In that case, don't
2310 // re-run EndDownload.
2317 void nsWebBrowserPersist::EndDownload(nsresult aResult
) {
2318 MOZ_ASSERT(NS_IsMainThread(), "Should end download on the main thread.");
2320 // Really this should just never happen, but if it does, at least avoid
2321 // no-op notifications or pretending we succeeded if we already failed.
2322 if (mEndCalled
&& (NS_SUCCEEDED(aResult
) || mPersistResult
== aResult
)) {
2326 // Store the error code in the result if it is an error
2327 if (NS_SUCCEEDED(mPersistResult
) && NS_FAILED(aResult
)) {
2328 mPersistResult
= aResult
;
2332 MOZ_ASSERT(!mEndCalled
, "Should only end the download once.");
2337 ClosePromise::All(GetCurrentSerialEventTarget(), mFileClosePromises
)
2338 ->Then(GetCurrentSerialEventTarget(), __func__
,
2339 [self
= RefPtr
{this}, aResult
]() {
2340 self
->EndDownloadInternal(aResult
);
2344 void nsWebBrowserPersist::EndDownloadInternal(nsresult aResult
) {
2345 // mCompleted needs to be set before issuing the stop notification.
2348 // State stop notification
2349 if (mProgressListener
) {
2350 mProgressListener
->OnStateChange(
2352 nsIWebProgressListener::STATE_STOP
|
2353 nsIWebProgressListener::STATE_IS_NETWORK
,
2357 // Do file cleanup if required
2358 if (NS_FAILED(aResult
) &&
2359 (mPersistFlags
& PERSIST_FLAGS_CLEANUP_ON_FAILURE
)) {
2360 CleanupLocalFiles();
2363 // Cleanup the channels
2366 mProgressListener
= nullptr;
2367 mProgressListener2
= nullptr;
2368 mEventSink
= nullptr;
2371 nsresult
nsWebBrowserPersist::FixRedirectedChannelEntry(
2372 nsIChannel
* aNewChannel
) {
2373 NS_ENSURE_ARG_POINTER(aNewChannel
);
2375 // Iterate through existing open channels looking for one with a URI
2376 // matching the one specified.
2377 nsCOMPtr
<nsIURI
> originalURI
;
2378 aNewChannel
->GetOriginalURI(getter_AddRefs(originalURI
));
2379 nsISupports
* matchingKey
= nullptr;
2380 for (nsISupports
* key
: mOutputMap
.Keys()) {
2381 nsCOMPtr
<nsIChannel
> thisChannel
= do_QueryInterface(key
);
2382 nsCOMPtr
<nsIURI
> thisURI
;
2384 thisChannel
->GetOriginalURI(getter_AddRefs(thisURI
));
2386 // Compare this channel's URI to the one passed in.
2387 bool matchingURI
= false;
2388 thisURI
->Equals(originalURI
, &matchingURI
);
2396 // We only get called from OnStartRequest, so this is always on the
2397 // main thread. Make sure we don't pull the rug from under anything else.
2398 MutexAutoLock
lock(mOutputMapMutex
);
2399 // If a match was found, remove the data entry with the old channel
2400 // key and re-add it with the new channel key.
2401 mozilla::UniquePtr
<OutputData
> outputData
;
2402 mOutputMap
.Remove(matchingKey
, &outputData
);
2403 NS_ENSURE_TRUE(outputData
, NS_ERROR_FAILURE
);
2405 // Store data again with new channel unless told to ignore redirects.
2406 if (!(mPersistFlags
& PERSIST_FLAGS_IGNORE_REDIRECTED_DATA
)) {
2407 nsCOMPtr
<nsISupports
> keyPtr
= do_QueryInterface(aNewChannel
);
2408 mOutputMap
.InsertOrUpdate(keyPtr
, std::move(outputData
));
2415 void nsWebBrowserPersist::CalcTotalProgress() {
2416 mTotalCurrentProgress
= 0;
2417 mTotalMaxProgress
= 0;
2419 if (mOutputMap
.Count() > 0) {
2420 // Total up the progress of each output stream
2421 for (const auto& data
: mOutputMap
.Values()) {
2422 // Only count toward total progress if destination file is local.
2423 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(data
->mFile
);
2425 mTotalCurrentProgress
+= data
->mSelfProgress
;
2426 mTotalMaxProgress
+= data
->mSelfProgressMax
;
2431 if (mUploadList
.Count() > 0) {
2432 // Total up the progress of each upload
2433 for (const auto& data
: mUploadList
.Values()) {
2435 mTotalCurrentProgress
+= data
->mSelfProgress
;
2436 mTotalMaxProgress
+= data
->mSelfProgressMax
;
2441 // XXX this code seems pretty bogus and pointless
2442 if (mTotalCurrentProgress
== 0 && mTotalMaxProgress
== 0) {
2443 // No output streams so we must be complete
2444 mTotalCurrentProgress
= 10000;
2445 mTotalMaxProgress
= 10000;
2449 nsresult
nsWebBrowserPersist::StoreURI(const nsACString
& aURI
,
2450 nsIWebBrowserPersistDocument
* aDoc
,
2451 nsContentPolicyType aContentPolicyType
,
2452 bool aNeedsPersisting
, URIData
** aData
) {
2453 nsCOMPtr
<nsIURI
> uri
;
2454 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aURI
, mCurrentCharset
.get(),
2456 NS_ENSURE_SUCCESS(rv
, rv
);
2458 return StoreURI(uri
, aDoc
, aContentPolicyType
, aNeedsPersisting
, aData
);
2461 nsresult
nsWebBrowserPersist::StoreURI(nsIURI
* aURI
,
2462 nsIWebBrowserPersistDocument
* aDoc
,
2463 nsContentPolicyType aContentPolicyType
,
2464 bool aNeedsPersisting
, URIData
** aData
) {
2465 NS_ENSURE_ARG_POINTER(aURI
);
2470 // Test if this URI should be persisted. By default
2471 // we should assume the URI is persistable.
2472 bool doNotPersistURI
;
2473 nsresult rv
= NS_URIChainHasFlags(
2474 aURI
, nsIProtocolHandler::URI_NON_PERSISTABLE
, &doNotPersistURI
);
2475 if (NS_FAILED(rv
)) {
2476 doNotPersistURI
= false;
2479 if (doNotPersistURI
) {
2483 URIData
* data
= nullptr;
2484 MakeAndStoreLocalFilenameInURIMap(aURI
, aDoc
, aContentPolicyType
,
2485 aNeedsPersisting
, &data
);
2493 nsresult
nsWebBrowserPersist::URIData::GetLocalURI(nsIURI
* targetBaseURI
,
2494 nsCString
& aSpecOut
) {
2495 aSpecOut
.SetIsVoid(true);
2500 nsCOMPtr
<nsIURI
> fileAsURI
;
2504 fileAsURI
= mDataPath
;
2505 rv
= AppendPathToURI(fileAsURI
, mFilename
, fileAsURI
);
2506 NS_ENSURE_SUCCESS(rv
, rv
);
2509 // remove username/password if present
2510 Unused
<< NS_MutateURI(fileAsURI
).SetUserPass(""_ns
).Finalize(fileAsURI
);
2512 // reset node attribute
2513 // Use relative or absolute links
2514 if (mDataPathIsRelative
) {
2515 bool isEqual
= false;
2516 if (NS_SUCCEEDED(mRelativeDocumentURI
->Equals(targetBaseURI
, &isEqual
)) &&
2518 nsCOMPtr
<nsIURL
> url(do_QueryInterface(fileAsURI
));
2520 return NS_ERROR_FAILURE
;
2523 nsAutoCString filename
;
2524 url
->GetFileName(filename
);
2526 nsAutoCString
rawPathURL(mRelativePathToData
);
2527 rawPathURL
.Append(filename
);
2529 rv
= NS_EscapeURL(rawPathURL
, esc_FilePath
, aSpecOut
, fallible
);
2530 NS_ENSURE_SUCCESS(rv
, rv
);
2532 nsAutoCString rawPathURL
;
2534 nsCOMPtr
<nsIFile
> dataFile
;
2535 rv
= GetLocalFileFromURI(mFile
, getter_AddRefs(dataFile
));
2536 NS_ENSURE_SUCCESS(rv
, rv
);
2538 nsCOMPtr
<nsIFile
> docFile
;
2539 rv
= GetLocalFileFromURI(targetBaseURI
, getter_AddRefs(docFile
));
2540 NS_ENSURE_SUCCESS(rv
, rv
);
2542 nsCOMPtr
<nsIFile
> parentDir
;
2543 rv
= docFile
->GetParent(getter_AddRefs(parentDir
));
2544 NS_ENSURE_SUCCESS(rv
, rv
);
2546 rv
= dataFile
->GetRelativePath(parentDir
, rawPathURL
);
2547 NS_ENSURE_SUCCESS(rv
, rv
);
2549 rv
= NS_EscapeURL(rawPathURL
, esc_FilePath
, aSpecOut
, fallible
);
2550 NS_ENSURE_SUCCESS(rv
, rv
);
2553 fileAsURI
->GetSpec(aSpecOut
);
2556 AppendUTF16toUTF8(mSubFrameExt
, aSpecOut
);
2562 bool nsWebBrowserPersist::DocumentEncoderExists(const char* aContentType
) {
2563 return do_getDocumentTypeSupportedForEncoding(aContentType
);
2566 nsresult
nsWebBrowserPersist::SaveSubframeContent(
2567 nsIWebBrowserPersistDocument
* aFrameContent
,
2568 nsIWebBrowserPersistDocument
* aParentDocument
, const nsCString
& aURISpec
,
2570 NS_ENSURE_ARG_POINTER(aData
);
2572 // Extract the content type for the frame's contents.
2573 nsAutoCString contentType
;
2574 nsresult rv
= aFrameContent
->GetContentType(contentType
);
2575 NS_ENSURE_SUCCESS(rv
, rv
);
2578 GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType
).get(),
2579 getter_Copies(ext
));
2581 // We must always have an extension so we will try to re-assign
2582 // the original extension if GetExtensionForContentType fails.
2583 if (ext
.IsEmpty()) {
2584 nsCOMPtr
<nsIURI
> docURI
;
2585 rv
= NS_NewURI(getter_AddRefs(docURI
), aURISpec
, mCurrentCharset
.get());
2586 NS_ENSURE_SUCCESS(rv
, rv
);
2588 nsCOMPtr
<nsIURL
> url(do_QueryInterface(docURI
, &rv
));
2589 nsAutoCString extension
;
2590 if (NS_SUCCEEDED(rv
)) {
2591 url
->GetFileExtension(extension
);
2593 extension
.AssignLiteral("htm");
2595 aData
->mSubFrameExt
.Assign(char16_t('.'));
2596 AppendUTF8toUTF16(extension
, aData
->mSubFrameExt
);
2598 aData
->mSubFrameExt
.Assign(char16_t('.'));
2599 aData
->mSubFrameExt
.Append(ext
);
2602 nsString filenameWithExt
= aData
->mFilename
;
2603 filenameWithExt
.Append(aData
->mSubFrameExt
);
2605 // Work out the path for the subframe
2606 nsCOMPtr
<nsIURI
> frameURI
= mCurrentDataPath
;
2607 rv
= AppendPathToURI(frameURI
, filenameWithExt
, frameURI
);
2608 NS_ENSURE_SUCCESS(rv
, rv
);
2610 // Work out the path for the subframe data
2611 nsCOMPtr
<nsIURI
> frameDataURI
= mCurrentDataPath
;
2612 nsAutoString
newFrameDataPath(aData
->mFilename
);
2615 newFrameDataPath
.AppendLiteral("_data");
2616 rv
= AppendPathToURI(frameDataURI
, newFrameDataPath
, frameDataURI
);
2617 NS_ENSURE_SUCCESS(rv
, rv
);
2619 // Make frame document & data path conformant and unique
2620 nsCOMPtr
<nsIURI
> out
;
2621 rv
= CalculateUniqueFilename(frameURI
, out
);
2622 NS_ENSURE_SUCCESS(rv
, rv
);
2625 rv
= CalculateUniqueFilename(frameDataURI
, out
);
2626 NS_ENSURE_SUCCESS(rv
, rv
);
2629 mCurrentThingsToPersist
++;
2631 // We shouldn't use SaveDocumentInternal for the contents
2632 // of frames that are not documents, e.g. images.
2633 if (DocumentEncoderExists(contentType
.get())) {
2634 auto toWalk
= mozilla::MakeUnique
<WalkData
>();
2635 toWalk
->mDocument
= aFrameContent
;
2636 toWalk
->mFile
= frameURI
;
2637 toWalk
->mDataPath
= frameDataURI
;
2638 mWalkStack
.AppendElement(std::move(toWalk
));
2640 nsContentPolicyType policyType
= nsIContentPolicy::TYPE_OTHER
;
2641 if (StringBeginsWith(contentType
, "image/"_ns
)) {
2642 policyType
= nsIContentPolicy::TYPE_IMAGE
;
2643 } else if (StringBeginsWith(contentType
, "audio/"_ns
) ||
2644 StringBeginsWith(contentType
, "video/"_ns
)) {
2645 policyType
= nsIContentPolicy::TYPE_MEDIA
;
2647 rv
= StoreURI(aURISpec
, aParentDocument
, policyType
);
2649 NS_ENSURE_SUCCESS(rv
, rv
);
2651 // Store the updated uri to the frame
2652 aData
->mFile
= frameURI
;
2653 aData
->mSubFrameExt
.Truncate(); // we already put this in frameURI
2658 nsresult
nsWebBrowserPersist::CreateChannelFromURI(nsIURI
* aURI
,
2659 nsIChannel
** aChannel
) {
2660 nsresult rv
= NS_OK
;
2661 *aChannel
= nullptr;
2663 rv
= NS_NewChannel(aChannel
, aURI
, nsContentUtils::GetSystemPrincipal(),
2664 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
2665 nsIContentPolicy::TYPE_OTHER
);
2666 NS_ENSURE_SUCCESS(rv
, rv
);
2667 NS_ENSURE_ARG_POINTER(*aChannel
);
2669 rv
= (*aChannel
)->SetNotificationCallbacks(
2670 static_cast<nsIInterfaceRequestor
*>(this));
2671 NS_ENSURE_SUCCESS(rv
, rv
);
2675 // we store the current location as the key (absolutized version of domnode's
2676 // attribute's value)
2677 nsresult
nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap(
2678 nsIURI
* aURI
, nsIWebBrowserPersistDocument
* aDoc
,
2679 nsContentPolicyType aContentPolicyType
, bool aNeedsPersisting
,
2681 NS_ENSURE_ARG_POINTER(aURI
);
2684 nsresult rv
= aURI
->GetSpec(spec
);
2685 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
2687 // Create a sensibly named filename for the URI and store in the URI map
2689 if (mURIMap
.Get(spec
, &data
)) {
2690 if (aNeedsPersisting
) {
2691 data
->mNeedsPersisting
= true;
2699 // Create a unique file name for the uri
2701 rv
= MakeFilenameFromURI(aURI
, filename
);
2702 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
);
2704 // Store the file name
2707 data
->mContentPolicyType
= aContentPolicyType
;
2708 data
->mNeedsPersisting
= aNeedsPersisting
;
2709 data
->mNeedsFixup
= true;
2710 data
->mFilename
= filename
;
2711 data
->mSaved
= false;
2712 data
->mIsSubFrame
= false;
2713 data
->mDataPath
= mCurrentDataPath
;
2714 data
->mDataPathIsRelative
= mCurrentDataPathIsRelative
;
2715 data
->mRelativePathToData
= mCurrentRelativePathToData
;
2716 data
->mRelativeDocumentURI
= mTargetBaseURI
;
2717 data
->mCharset
= mCurrentCharset
;
2719 aDoc
->GetPrincipal(getter_AddRefs(data
->mTriggeringPrincipal
));
2720 aDoc
->GetCookieJarSettings(getter_AddRefs(data
->mCookieJarSettings
));
2722 if (aNeedsPersisting
) mCurrentThingsToPersist
++;
2724 mURIMap
.InsertOrUpdate(spec
, UniquePtr
<URIData
>(data
));
2732 // Decide if we need to apply conversion to the passed channel.
2733 void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel
* aChannel
) {
2734 nsresult rv
= NS_OK
;
2735 nsCOMPtr
<nsIEncodedChannel
> encChannel
= do_QueryInterface(aChannel
, &rv
);
2736 if (NS_FAILED(rv
)) return;
2738 // Set the default conversion preference:
2739 encChannel
->SetApplyConversion(false);
2741 nsCOMPtr
<nsIURI
> thisURI
;
2742 aChannel
->GetURI(getter_AddRefs(thisURI
));
2743 nsCOMPtr
<nsIURL
> sourceURL(do_QueryInterface(thisURI
));
2744 if (!sourceURL
) return;
2745 nsAutoCString extension
;
2746 sourceURL
->GetFileExtension(extension
);
2748 nsCOMPtr
<nsIUTF8StringEnumerator
> encEnum
;
2749 encChannel
->GetContentEncodings(getter_AddRefs(encEnum
));
2750 if (!encEnum
) return;
2751 nsCOMPtr
<nsIExternalHelperAppService
> helperAppService
=
2752 do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID
, &rv
);
2753 if (NS_FAILED(rv
)) return;
2755 rv
= encEnum
->HasMore(&hasMore
);
2756 if (NS_SUCCEEDED(rv
) && hasMore
) {
2757 nsAutoCString encType
;
2758 rv
= encEnum
->GetNext(encType
);
2759 if (NS_SUCCEEDED(rv
)) {
2760 bool applyConversion
= false;
2761 rv
= helperAppService
->ApplyDecodingForExtension(extension
, encType
,
2763 if (NS_SUCCEEDED(rv
)) encChannel
->SetApplyConversion(applyConversion
);