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/. */
5 
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/TextUtils.h"
8 
9 #include "nspr.h"
10 
11 #include "nsIFileStreams.h"  // New Necko file streams
12 #include <algorithm>
13 
14 #include "nsNetCID.h"
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"
28 #include "nsEscape.h"
29 #include "nsUnicharUtils.h"
30 #include "nsIStringEnumerator.h"
31 #include "nsContentCID.h"
32 #include "nsStreamUtils.h"
33 
34 #include "nsCExternalHandlerService.h"
35 
36 #include "nsIURL.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"
44 
45 #include "nsIStringBundle.h"
46 #include "nsIProtocolHandler.h"
47 
48 #include "nsWebBrowserPersist.h"
49 #include "WebBrowserPersistLocalDocument.h"
50 
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"
67 
68 using namespace mozilla;
69 using namespace mozilla::dom;
70 
71 // Buffer file writes in 32kb chunks
72 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
73 
74 struct nsWebBrowserPersist::WalkData {
75   nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
76   nsCOMPtr<nsIURI> mFile;
77   nsCOMPtr<nsIURI> mDataPath;
78 };
79 
80 // Information about a DOM document
81 struct nsWebBrowserPersist::DocData {
82   nsCOMPtr<nsIURI> mBaseURI;
83   nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
84   nsCOMPtr<nsIURI> mFile;
85   nsCString mCharset;
86 };
87 
88 // Information about a URI
89 struct nsWebBrowserPersist::URIData {
90   bool mNeedsPersisting;
91   bool mSaved;
92   bool mIsSubFrame;
93   bool mDataPathIsRelative;
94   bool mNeedsFixup;
95   nsString mFilename;
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;
104   nsCString mCharset;
105 
106   nsresult GetLocalURI(nsIURI* targetBaseURI, nsCString& aSpecOut);
107 };
108 
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;
124   Mutex mStreamMutex;
125   int64_t mSelfProgress;
126   int64_t mSelfProgressMax;
127   bool mCalcFileExt;
128 
OutputDatansWebBrowserPersist::OutputData129   OutputData(nsIURI* aFile, nsIURI* aOriginalLocation, bool aCalcFileExt)
130       : mFile(aFile),
131         mOriginalLocation(aOriginalLocation),
132         mStreamMutex("nsWebBrowserPersist::OutputData::mStreamMutex"),
133         mSelfProgress(0),
134         mSelfProgressMax(10000),
135         mCalcFileExt(aCalcFileExt) {}
~OutputDatansWebBrowserPersist::OutputData136   ~OutputData() {
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
140     // destroyed.
141     MutexAutoLock lock(mStreamMutex);
142     if (mStream) {
143       mStream->Close();
144     }
145   }
146 };
147 
148 struct nsWebBrowserPersist::UploadData {
149   nsCOMPtr<nsIURI> mFile;
150   int64_t mSelfProgress;
151   int64_t mSelfProgressMax;
152 
UploadDatansWebBrowserPersist::UploadData153   explicit UploadData(nsIURI* aFile)
154       : mFile(aFile), mSelfProgress(0), mSelfProgressMax(10000) {}
155 };
156 
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.
162   bool mIsDirectory;
163 };
164 
165 class nsWebBrowserPersist::OnWalk final
166     : public nsIWebBrowserPersistResourceVisitor {
167  public:
OnWalk(nsWebBrowserPersist * aParent,nsIURI * aFile,nsIFile * aDataPath)168   OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath)
169       : mParent(aParent),
170         mFile(aFile),
171         mDataPath(aDataPath),
172         mPendingDocuments(1),
173         mStatus(NS_OK) {}
174 
175   NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
176   NS_DECL_ISUPPORTS
177  private:
178   RefPtr<nsWebBrowserPersist> mParent;
179   nsCOMPtr<nsIURI> mFile;
180   nsCOMPtr<nsIFile> mDataPath;
181 
182   uint32_t mPendingDocuments;
183   nsresult mStatus;
184 
185   virtual ~OnWalk() = default;
186 };
187 
188 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
189                   nsIWebBrowserPersistResourceVisitor)
190 
191 class nsWebBrowserPersist::OnRemoteWalk final
192     : public nsIWebBrowserPersistDocumentReceiver {
193  public:
OnRemoteWalk(nsIWebBrowserPersistResourceVisitor * aVisitor,nsIWebBrowserPersistDocument * aDocument)194   OnRemoteWalk(nsIWebBrowserPersistResourceVisitor* aVisitor,
195                nsIWebBrowserPersistDocument* aDocument)
196       : mVisitor(aVisitor), mDocument(aDocument) {}
197 
198   NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
199   NS_DECL_ISUPPORTS
200  private:
201   nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
202   nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
203 
204   virtual ~OnRemoteWalk() = default;
205 };
206 
207 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnRemoteWalk,
208                   nsIWebBrowserPersistDocumentReceiver)
209 
210 class nsWebBrowserPersist::OnWrite final
211     : public nsIWebBrowserPersistWriteCompletion {
212  public:
OnWrite(nsWebBrowserPersist * aParent,nsIURI * aFile,nsIFile * aLocalFile)213   OnWrite(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aLocalFile)
214       : mParent(aParent), mFile(aFile), mLocalFile(aLocalFile) {}
215 
216   NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
217   NS_DECL_ISUPPORTS
218  private:
219   RefPtr<nsWebBrowserPersist> mParent;
220   nsCOMPtr<nsIURI> mFile;
221   nsCOMPtr<nsIFile> mLocalFile;
222 
223   virtual ~OnWrite() = default;
224 };
225 
226 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite,
227                   nsIWebBrowserPersistWriteCompletion)
228 
229 class nsWebBrowserPersist::FlatURIMap final
230     : public nsIWebBrowserPersistURIMap {
231  public:
FlatURIMap(const nsACString & aTargetBase)232   explicit FlatURIMap(const nsACString& aTargetBase)
233       : mTargetBase(aTargetBase) {}
234 
Add(const nsACString & aMapFrom,const nsACString & aMapTo)235   void Add(const nsACString& aMapFrom, const nsACString& aMapTo) {
236     mMapFrom.AppendElement(aMapFrom);
237     mMapTo.AppendElement(aMapTo);
238   }
239 
240   NS_DECL_NSIWEBBROWSERPERSISTURIMAP
241   NS_DECL_ISUPPORTS
242 
243  private:
244   nsTArray<nsCString> mMapFrom;
245   nsTArray<nsCString> mMapTo;
246   nsCString mTargetBase;
247 
248   virtual ~FlatURIMap() = default;
249 };
250 
NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap,nsIWebBrowserPersistURIMap)251 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap)
252 
253 NS_IMETHODIMP
254 nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum) {
255   MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
256   *aNum = mMapTo.Length();
257   return NS_OK;
258 }
259 
260 NS_IMETHODIMP
GetTargetBaseURI(nsACString & aTargetBase)261 nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase) {
262   aTargetBase = mTargetBase;
263   return NS_OK;
264 }
265 
266 NS_IMETHODIMP
GetURIMapping(uint32_t aIndex,nsACString & aMapFrom,nsACString & aMapTo)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;
273   }
274   aMapFrom = mMapFrom[aIndex];
275   aMapTo = mMapTo[aIndex];
276   return NS_OK;
277 }
278 
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;
284 
285 // Default flags for persistence
286 const uint32_t kDefaultPersistFlags =
287     nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
288     nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES;
289 
290 // String bundle where error messages come from
291 const char* kWebBrowserPersistStringBundle =
292     "chrome://global/locale/nsWebBrowserPersist.properties";
293 
nsWebBrowserPersist()294 nsWebBrowserPersist::nsWebBrowserPersist()
295     : mCurrentDataPathIsRelative(false),
296       mCurrentThingsToPersist(0),
297       mOutputMapMutex("nsWebBrowserPersist::mOutputMapMutex"),
298       mFirstAndOnlyUse(true),
299       mSavingDocument(false),
300       mCancel(false),
301       mEndCalled(false),
302       mCompleted(false),
303       mStartSaving(false),
304       mReplaceExisting(true),
305       mSerializingOutput(false),
306       mIsPrivate(false),
307       mPersistFlags(kDefaultPersistFlags),
308       mPersistResult(NS_OK),
309       mTotalCurrentProgress(0),
310       mTotalMaxProgress(0),
311       mWrapColumn(72),
312       mEncodingFlags(0) {}
313 
~nsWebBrowserPersist()314 nsWebBrowserPersist::~nsWebBrowserPersist() { Cleanup(); }
315 
316 //*****************************************************************************
317 // nsWebBrowserPersist::nsISupports
318 //*****************************************************************************
319 
320 NS_IMPL_ADDREF(nsWebBrowserPersist)
NS_IMPL_RELEASE(nsWebBrowserPersist)321 NS_IMPL_RELEASE(nsWebBrowserPersist)
322 
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)
333 NS_INTERFACE_MAP_END
334 
335 //*****************************************************************************
336 // nsWebBrowserPersist::nsIInterfaceRequestor
337 //*****************************************************************************
338 
339 NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID& aIID,
340                                                 void** aIFace) {
341   NS_ENSURE_ARG_POINTER(aIFace);
342 
343   *aIFace = nullptr;
344 
345   nsresult rv = QueryInterface(aIID, aIFace);
346   if (NS_SUCCEEDED(rv)) {
347     return rv;
348   }
349 
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;
354   }
355 
356   nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener);
357   if (req) {
358     return req->GetInterface(aIID, aIFace);
359   }
360 
361   return NS_ERROR_NO_INTERFACE;
362 }
363 
364 //*****************************************************************************
365 // nsWebBrowserPersist::nsIWebBrowserPersist
366 //*****************************************************************************
367 
GetPersistFlags(uint32_t * aPersistFlags)368 NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t* aPersistFlags) {
369   NS_ENSURE_ARG_POINTER(aPersistFlags);
370   *aPersistFlags = mPersistFlags;
371   return NS_OK;
372 }
SetPersistFlags(uint32_t aPersistFlags)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);
377   return NS_OK;
378 }
379 
GetCurrentState(uint32_t * aCurrentState)380 NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t* aCurrentState) {
381   NS_ENSURE_ARG_POINTER(aCurrentState);
382   if (mCompleted) {
383     *aCurrentState = PERSIST_STATE_FINISHED;
384   } else if (mFirstAndOnlyUse) {
385     *aCurrentState = PERSIST_STATE_SAVING;
386   } else {
387     *aCurrentState = PERSIST_STATE_READY;
388   }
389   return NS_OK;
390 }
391 
GetResult(nsresult * aResult)392 NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult* aResult) {
393   NS_ENSURE_ARG_POINTER(aResult);
394   *aResult = mPersistResult;
395   return NS_OK;
396 }
397 
GetProgressListener(nsIWebProgressListener ** aProgressListener)398 NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener(
399     nsIWebProgressListener** aProgressListener) {
400   NS_ENSURE_ARG_POINTER(aProgressListener);
401   *aProgressListener = mProgressListener;
402   NS_IF_ADDREF(*aProgressListener);
403   return NS_OK;
404 }
405 
SetProgressListener(nsIWebProgressListener * aProgressListener)406 NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener(
407     nsIWebProgressListener* aProgressListener) {
408   mProgressListener = aProgressListener;
409   mProgressListener2 = do_QueryInterface(aProgressListener);
410   mEventSink = do_GetInterface(aProgressListener);
411   return NS_OK;
412 }
413 
SaveURI(nsIURI * aURI,nsIPrincipal * aPrincipal,uint32_t aCacheKey,nsIReferrerInfo * aReferrerInfo,nsICookieJarSettings * aCookieJarSettings,nsIInputStream * aPostData,const char * aExtraHeaders,nsISupports * aFile,nsContentPolicyType aContentPolicyType,nsILoadContext * aPrivacyContext)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);
423 }
424 
SavePrivacyAwareURI(nsIURI * aURI,nsIPrincipal * aPrincipal,uint32_t aCacheKey,nsIReferrerInfo * aReferrerInfo,nsICookieJarSettings * aCookieJarSettings,nsIInputStream * aPostData,const char * aExtraHeaders,nsISupports * aFile,nsContentPolicyType aContentPolicy,bool aIsPrivate)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!
432 
433   nsCOMPtr<nsIURI> fileAsURI;
434   nsresult rv;
435   rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
436   NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
437 
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;
444 }
445 
SaveChannel(nsIChannel * aChannel,nsISupports * aFile)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!
450 
451   nsCOMPtr<nsIURI> fileAsURI;
452   nsresult rv;
453   rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
454   NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
455 
456   rv = aChannel->GetURI(getter_AddRefs(mURI));
457   NS_ENSURE_SUCCESS(rv, rv);
458 
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;
463 }
464 
SaveDocument(nsISupports * aDocument,nsISupports * aFile,nsISupports * aDataPath,const char * aOutputContentType,uint32_t aEncodingFlags,uint32_t aWrapColumn)465 NS_IMETHODIMP nsWebBrowserPersist::SaveDocument(nsISupports* aDocument,
466                                                 nsISupports* aFile,
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!
473 
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;
479 
480   NS_ENSURE_ARG_POINTER(aDocument);
481   NS_ENSURE_ARG_POINTER(aFile);
482 
483   nsCOMPtr<nsIURI> fileAsURI;
484   nsCOMPtr<nsIURI> datapathAsURI;
485   nsresult rv;
486 
487   rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
488   NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
489   if (aDataPath) {
490     rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI));
491     NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
492   }
493 
494   mWrapColumn = aWrapColumn;
495   mEncodingFlags = aEncodingFlags;
496 
497   if (aOutputContentType) {
498     mContentType.AssignASCII(aOutputContentType);
499   }
500 
501   // State start notification
502   if (mProgressListener) {
503     mProgressListener->OnStateChange(
504         nullptr, nullptr,
505         nsIWebProgressListener::STATE_START |
506             nsIWebProgressListener::STATE_IS_NETWORK,
507         NS_OK);
508   }
509 
510   nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument);
511   if (!doc) {
512     nsCOMPtr<Document> localDoc = do_QueryInterface(aDocument);
513     if (localDoc) {
514       doc = new mozilla::WebBrowserPersistLocalDocument(localDoc);
515     } else {
516       rv = NS_ERROR_NO_INTERFACE;
517     }
518   }
519 
520   bool closed = false;
521   if (doc && NS_SUCCEEDED(doc->GetIsClosed(&closed)) && !closed) {
522     rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI);
523   }
524 
525   if (NS_FAILED(rv) || closed) {
526     SendErrorStatusChange(true, rv, nullptr, mURI);
527     EndDownload(rv);
528   }
529   return rv;
530 }
531 
Cancel(nsresult aReason)532 NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) {
533   // No point cancelling if we're already complete.
534   if (mEndCalled) {
535     return NS_OK;
536   }
537   mCancel = true;
538   EndDownload(aReason);
539   return NS_OK;
540 }
541 
CancelSave()542 NS_IMETHODIMP nsWebBrowserPersist::CancelSave() {
543   return Cancel(NS_BINDING_ABORTED);
544 }
545 
StartUpload(nsIStorageStream * storStream,nsIURI * aDestinationURI,const nsACString & aContentType)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);
555 }
556 
StartUpload(nsIInputStream * aInputStream,nsIURI * aDestinationURI,const nsACString & 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);
564 
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);
571 
572   // add this to the upload list
573   nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
574   mUploadList.InsertOrUpdate(keyPtr, MakeUnique<UploadData>(aDestinationURI));
575 
576   return NS_OK;
577 }
578 
SerializeNextFile()579 void nsWebBrowserPersist::SerializeNextFile() {
580   nsresult rv = NS_OK;
581   MOZ_ASSERT(mWalkStack.Length() == 0);
582 
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.
588 
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();
593 
594     if (!data->mNeedsPersisting || data->mSaved) {
595       continue;
596     }
597 
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))) {
602       break;
603     }
604 
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))) {
609       break;
610     }
611 
612     rv = SaveURIInternal(uri, data->mTriggeringPrincipal,
613                          data->mContentPolicyType, 0, nullptr,
614                          data->mCookieJarSettings, nullptr, nullptr, fileAsURI,
615                          true, mIsPrivate);
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))) {
619       break;
620     }
621 
622     if (rv == NS_OK) {
623       // URIData.mFile will be updated to point to the correct
624       // URI object when it is fixed up with the right file extension
625       // in OnStartRequest
626       data->mFile = fileAsURI;
627       data->mSaved = true;
628     } else {
629       data->mNeedsFixup = false;
630     }
631 
632     if (mSerializingOutput) {
633       break;
634     }
635   }
636 
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) {
640     return;
641   }
642 
643   // If serializing, also wait until last upload is done.
644   if (mSerializingOutput && mUploadList.Count() > 0) {
645     return;
646   }
647 
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) {
652       return;
653     }
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));
660     return;
661   }
662 
663   // There are no URIs to save, so just save the next document.
664   mStartSaving = true;
665   mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0));
666   mDocList.RemoveElementAt(0);  // O(n^2) but probably doesn't matter.
667   MOZ_ASSERT(docData);
668   if (!docData) {
669     EndDownload(NS_ERROR_FAILURE);
670     return;
671   }
672 
673   mCurrentBaseURI = docData->mBaseURI;
674   mCurrentCharset = docData->mCharset;
675   mTargetBaseURI = docData->mFile;
676 
677   // Save the document, fixing it up with the new URIs as we do
678 
679   nsAutoCString targetBaseSpec;
680   if (mTargetBaseURI) {
681     rv = mTargetBaseURI->GetSpec(targetBaseSpec);
682     if (NS_FAILED(rv)) {
683       SendErrorStatusChange(true, rv, nullptr, nullptr);
684       EndDownload(rv);
685       return;
686     }
687   }
688 
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) {
694     nsAutoCString mapTo;
695     nsresult rv = uriEntry.GetWeak()->GetLocalURI(mTargetBaseURI, mapTo);
696     if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
697       flatMap->Add(uriEntry.GetKey(), mapTo);
698     }
699   }
700   mFlatURIMap = std::move(flatMap);
701 
702   nsCOMPtr<nsIFile> localFile;
703   GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile));
704   if (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;
711     }
712     if (NS_FAILED(rv)) {
713       SendErrorStatusChange(false, rv, nullptr, docData->mFile);
714       EndDownload(rv);
715       return;
716     }
717   }
718   nsCOMPtr<nsIOutputStream> outputStream;
719   rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream));
720   if (NS_SUCCEEDED(rv) && !outputStream) {
721     rv = NS_ERROR_FAILURE;
722   }
723   if (NS_FAILED(rv)) {
724     SendErrorStatusChange(false, rv, nullptr, docData->mFile);
725     EndDownload(rv);
726     return;
727   }
728 
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);
733   if (NS_FAILED(rv)) {
734     SendErrorStatusChange(false, rv, nullptr, docData->mFile);
735     EndDownload(rv);
736   }
737 }
738 
739 NS_IMETHODIMP
OnFinish(nsIWebBrowserPersistDocument * aDoc,nsIOutputStream * aStream,const nsACString & aContentType,nsresult aStatus)740 nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc,
741                                        nsIOutputStream* aStream,
742                                        const nsACString& aContentType,
743                                        nsresult aStatus) {
744   nsresult rv = aStatus;
745 
746   if (NS_FAILED(rv)) {
747     mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
748     mParent->EndDownload(rv);
749     return NS_OK;
750   }
751   if (!mLocalFile) {
752     nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream));
753     if (storStream) {
754       aStream->Close();
755       rv = mParent->StartUpload(storStream, mFile, aContentType);
756       if (NS_FAILED(rv)) {
757         mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
758         mParent->EndDownload(rv);
759       }
760       // Either we failed and we're done, or we're uploading and
761       // the OnStopRequest callback is responsible for the next
762       // SerializeNextFile().
763       return NS_OK;
764     }
765   }
766   NS_DispatchToCurrentThread(
767       NewRunnableMethod("nsWebBrowserPersist::SerializeNextFile", mParent,
768                         &nsWebBrowserPersist::SerializeNextFile));
769   return NS_OK;
770 }
771 
772 //*****************************************************************************
773 // nsWebBrowserPersist::nsIRequestObserver
774 //*****************************************************************************
775 
OnStartRequest(nsIRequest * request)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;
782     }
783     mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK);
784   }
785 
786   nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
787   NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
788 
789   nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
790   OutputData* data = mOutputMap.Get(keyPtr);
791 
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.
796   if (!data) {
797     UploadData* upData = mUploadList.Get(keyPtr);
798     if (!upData) {
799       // Redirect? Try and fixup the output table
800       nsresult rv = FixRedirectedChannelEntry(channel);
801       NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
802 
803       // Should be able to find the data after fixup unless redirects
804       // are disabled.
805       data = mOutputMap.Get(keyPtr);
806       if (!data) {
807         return NS_ERROR_FAILURE;
808       }
809     }
810   }
811 
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));
821       }
822       if (mBackgroundQueue) {
823         r->RetargetDeliveryTo(mBackgroundQueue);
824       }
825     }
826 
827     // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags,
828     // try to determine whether this channel needs to apply Content-Encoding
829     // conversions.
830     NS_ASSERTION(
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);
836 
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;
845       }
846 
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;
852       }
853 
854       // The URIData entry is pointing to the old unfixed URI, so we need
855       // to update it.
856       nsCOMPtr<nsIURI> chanURI;
857       rv = channel->GetOriginalURI(getter_AddRefs(chanURI));
858       if (NS_SUCCEEDED(rv)) {
859         nsAutoCString spec;
860         chanURI->GetSpec(spec);
861         URIData* uridata;
862         if (mURIMap.Get(spec, &uridata)) {
863           uridata->mFile = data->mFile;
864         }
865       }
866     }
867 
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)) &&
871         isEqual) {
872       {
873         MutexAutoLock lock(mOutputMapMutex);
874         // remove from output map
875         mOutputMap.Remove(keyPtr);
876       }
877 
878       // cancel; we don't need to know any more
879       // stop request will get called
880       request->Cancel(NS_BINDING_ABORTED);
881     }
882   }
883 
884   return NS_OK;
885 }
886 
OnStopRequest(nsIRequest * request,nsresult status)887 NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest(nsIRequest* request,
888                                                  nsresult status) {
889   nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
890   OutputData* data = mOutputMap.Get(keyPtr);
891   if (data) {
892     if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) {
893       SendErrorStatusChange(true, status, request, data->mFile);
894     }
895 
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.
901     {
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));
907           if (NS_FAILED(rv)) {
908             return rv;
909           }
910         }
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__);
920             }));
921       }
922     }
923     MutexAutoLock lock(mOutputMapMutex);
924     mOutputMap.Remove(keyPtr);
925   } else {
926     // if we didn't find the data in mOutputMap, try mUploadList
927     UploadData* upData = mUploadList.Get(keyPtr);
928     if (upData) {
929       mUploadList.Remove(keyPtr);
930     }
931   }
932 
933   // Do more work.
934   SerializeNextFile();
935 
936   if (mProgressListener) {
937     uint32_t stateFlags = nsIWebProgressListener::STATE_STOP |
938                           nsIWebProgressListener::STATE_IS_REQUEST;
939     if (!mSavingDocument) {
940       stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
941     }
942     mProgressListener->OnStateChange(nullptr, request, stateFlags, status);
943   }
944 
945   return NS_OK;
946 }
947 
948 //*****************************************************************************
949 // nsWebBrowserPersist::nsIStreamListener
950 //*****************************************************************************
951 
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
959 // this in mind.
960 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsIInputStream * aIStream,uint64_t aOffset,uint32_t aLength)961 nsWebBrowserPersist::OnDataAvailable(nsIRequest* request,
962                                      nsIInputStream* aIStream, uint64_t aOffset,
963                                      uint32_t aLength) {
964   // MOZ_ASSERT(!NS_IsMainThread()); // no guarantees, but it's likely.
965 
966   bool cancel = mCancel;
967   if (!cancel) {
968     nsresult rv = NS_OK;
969     uint32_t bytesRemaining = aLength;
970 
971     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
972     NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
973 
974     MutexAutoLock lock(mOutputMapMutex);
975     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
976     OutputData* data = mOutputMap.Get(keyPtr);
977     if (!data) {
978       // might be uploadData; consume necko's buffer and bail...
979       uint32_t n;
980       return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n);
981     }
982 
983     bool readError = true;
984 
985     MutexAutoLock streamLock(data->mStreamMutex);
986     // Make the output stream
987     if (!data->mStream) {
988       rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream));
989       if (NS_FAILED(rv)) {
990         readError = false;
991         cancel = true;
992       }
993     }
994 
995     // Read data from the input and write to the output
996     char buffer[8192];
997     uint32_t bytesRead;
998     while (!cancel && bytesRemaining) {
999       readError = true;
1000       rv = aIStream->Read(buffer,
1001                           std::min(uint32_t(sizeof(buffer)), bytesRemaining),
1002                           &bytesRead);
1003       if (NS_SUCCEEDED(rv)) {
1004         readError = false;
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
1009         // error code.
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;
1022               cancel = true;
1023             }
1024           } else {
1025             // Disaster - can't write out the bytes - disk full / permission?
1026             cancel = true;
1027           }
1028         }
1029       } else {
1030         // Disaster - can't read the bytes - broken link / file error?
1031         cancel = true;
1032       }
1033     }
1034 
1035     int64_t channelContentLength = -1;
1036     if (!cancel &&
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
1051         // file
1052         nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream));
1053         if (storStream) {
1054           data->mStream->Close();
1055           data->mStream =
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)) {
1061             readError = false;
1062             cancel = true;
1063           }
1064         }
1065       }
1066     }
1067 
1068     // Notify listener if an error occurred.
1069     if (cancel) {
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);
1076           });
1077       NS_DispatchToMainThread(errorOnMainThread);
1078 
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);
1084     }
1085   }
1086 
1087   return cancel ? NS_BINDING_ABORTED : NS_OK;
1088 }
1089 
1090 //*****************************************************************************
1091 // nsWebBrowserPersist::nsIThreadRetargetableStreamListener
1092 //*****************************************************************************
1093 
CheckListenerChain()1094 NS_IMETHODIMP nsWebBrowserPersist::CheckListenerChain() { return NS_OK; }
1095 
1096 //*****************************************************************************
1097 // nsWebBrowserPersist::nsIProgressEventSink
1098 //*****************************************************************************
1099 
OnProgress(nsIRequest * request,int64_t aProgress,int64_t aProgressMax)1100 NS_IMETHODIMP nsWebBrowserPersist::OnProgress(nsIRequest* request,
1101                                               int64_t aProgress,
1102                                               int64_t aProgressMax) {
1103   if (!mProgressListener) {
1104     return NS_OK;
1105   }
1106 
1107   // Store the progress of this request
1108   nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
1109   OutputData* data = mOutputMap.Get(keyPtr);
1110   if (data) {
1111     data->mSelfProgress = aProgress;
1112     data->mSelfProgressMax = aProgressMax;
1113   } else {
1114     UploadData* upData = mUploadList.Get(keyPtr);
1115     if (upData) {
1116       upData->mSelfProgress = aProgress;
1117       upData->mSelfProgressMax = aProgressMax;
1118     }
1119   }
1120 
1121   // Notify listener of total progress
1122   CalcTotalProgress();
1123   if (mProgressListener2) {
1124     mProgressListener2->OnProgressChange64(nullptr, request, aProgress,
1125                                            aProgressMax, mTotalCurrentProgress,
1126                                            mTotalMaxProgress);
1127   } else {
1128     // have to truncate 64-bit to 32bit
1129     mProgressListener->OnProgressChange(
1130         nullptr, request, uint64_t(aProgress), uint64_t(aProgressMax),
1131         mTotalCurrentProgress, mTotalMaxProgress);
1132   }
1133 
1134   // If our progress listener implements nsIProgressEventSink,
1135   // forward the notification
1136   if (mEventSink) {
1137     mEventSink->OnProgress(request, aProgress, aProgressMax);
1138   }
1139 
1140   return NS_OK;
1141 }
1142 
OnStatus(nsIRequest * request,nsresult status,const char16_t * statusArg)1143 NS_IMETHODIMP nsWebBrowserPersist::OnStatus(nsIRequest* request,
1144                                             nsresult status,
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?
1149     switch (status) {
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:
1161         break;
1162 
1163       default:
1164         // Pass other notifications (for legitimate errors) along.
1165         mProgressListener->OnStatusChange(nullptr, request, status, statusArg);
1166         break;
1167     }
1168   }
1169 
1170   // If our progress listener implements nsIProgressEventSink,
1171   // forward the notification
1172   if (mEventSink) {
1173     mEventSink->OnStatus(request, status, statusArg);
1174   }
1175 
1176   return NS_OK;
1177 }
1178 
1179 //*****************************************************************************
1180 // nsWebBrowserPersist private methods
1181 //*****************************************************************************
1182 
1183 // Convert error info into proper message text and send OnStatusChange
1184 // notification to the web progress listener.
SendErrorStatusChange(bool aIsReadError,nsresult aResult,nsIRequest * aRequest,nsIURI * aURI)1185 nsresult nsWebBrowserPersist::SendErrorStatusChange(bool aIsReadError,
1186                                                     nsresult aResult,
1187                                                     nsIRequest* aRequest,
1188                                                     nsIURI* aURI) {
1189   NS_ENSURE_ARG_POINTER(aURI);
1190 
1191   if (!mProgressListener) {
1192     // Do nothing
1193     return NS_OK;
1194   }
1195 
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;
1200   nsresult rv;
1201   if (file) {
1202     file->GetPath(*strings.AppendElement());
1203   } else {
1204     nsAutoCString fileurl;
1205     rv = aURI->GetSpec(fileurl);
1206     NS_ENSURE_SUCCESS(rv, rv);
1207     CopyUTF8toUTF16(fileurl, *strings.AppendElement());
1208   }
1209 
1210   const char* msgId;
1211   switch (aResult) {
1212     case NS_ERROR_FILE_NAME_TOO_LONG:
1213       // File name too long.
1214       msgId = "fileNameTooLongError";
1215       break;
1216     case NS_ERROR_FILE_ALREADY_EXISTS:
1217       // File exists with same name as directory.
1218       msgId = "fileAlreadyExistsError";
1219       break;
1220     case NS_ERROR_FILE_NO_DEVICE_SPACE:
1221       // Out of space on target volume.
1222       msgId = "diskFull";
1223       break;
1224 
1225     case NS_ERROR_FILE_READ_ONLY:
1226       // Attempt to write to read/only file.
1227       msgId = "readOnly";
1228       break;
1229 
1230     case NS_ERROR_FILE_ACCESS_DENIED:
1231       // Attempt to write without sufficient permissions.
1232       msgId = "accessError";
1233       break;
1234 
1235     default:
1236       // Generic read/write error message.
1237       if (aIsReadError)
1238         msgId = "readError";
1239       else
1240         msgId = "writeError";
1241       break;
1242   }
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);
1247 
1248   nsCOMPtr<nsIStringBundle> bundle;
1249   rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle));
1250   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE);
1251 
1252   nsAutoString msgText;
1253   rv = bundle->FormatStringFromName(msgId, strings, msgText);
1254   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1255 
1256   mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText.get());
1257 
1258   return NS_OK;
1259 }
1260 
GetValidURIFromObject(nsISupports * aObject,nsIURI ** aURI) const1261 nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports* aObject,
1262                                                     nsIURI** aURI) const {
1263   NS_ENSURE_ARG_POINTER(aObject);
1264   NS_ENSURE_ARG_POINTER(aURI);
1265 
1266   nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject);
1267   if (objAsFile) {
1268     return NS_NewFileURI(aURI, objAsFile);
1269   }
1270   nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject);
1271   if (objAsURI) {
1272     *aURI = objAsURI;
1273     NS_ADDREF(*aURI);
1274     return NS_OK;
1275   }
1276 
1277   return NS_ERROR_FAILURE;
1278 }
1279 
1280 /* static */
GetLocalFileFromURI(nsIURI * aURI,nsIFile ** aLocalFile)1281 nsresult nsWebBrowserPersist::GetLocalFileFromURI(nsIURI* aURI,
1282                                                   nsIFile** aLocalFile) {
1283   nsresult rv;
1284 
1285   nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
1286   if (NS_FAILED(rv)) return rv;
1287 
1288   nsCOMPtr<nsIFile> file;
1289   rv = fileURL->GetFile(getter_AddRefs(file));
1290   if (NS_FAILED(rv)) {
1291     return rv;
1292   }
1293 
1294   file.forget(aLocalFile);
1295   return NS_OK;
1296 }
1297 
1298 /* static */
AppendPathToURI(nsIURI * aURI,const nsAString & aPath,nsCOMPtr<nsIURI> & aOutURI)1299 nsresult nsWebBrowserPersist::AppendPathToURI(nsIURI* aURI,
1300                                               const nsAString& aPath,
1301                                               nsCOMPtr<nsIURI>& aOutURI) {
1302   NS_ENSURE_ARG_POINTER(aURI);
1303 
1304   nsAutoCString newPath;
1305   nsresult rv = aURI->GetPathQueryRef(newPath);
1306   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
1307 
1308   // Append a forward slash if necessary
1309   int32_t len = newPath.Length();
1310   if (len > 0 && newPath.CharAt(len - 1) != '/') {
1311     newPath.Append('/');
1312   }
1313 
1314   // Store the path back on the URI
1315   AppendUTF16toUTF8(aPath, newPath);
1316 
1317   return NS_MutateURI(aURI).SetPathQueryRef(newPath).Finalize(aOutURI);
1318 }
1319 
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)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);
1329 
1330   nsresult rv = NS_OK;
1331 
1332   mURI = aURI;
1333 
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;
1339   }
1340 
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) {
1346     cookieJarSettings =
1347         aIsPrivate
1348             ? net::CookieJarSettings::Create(net::CookieJarSettings::ePrivate)
1349             : net::CookieJarSettings::Create(net::CookieJarSettings::eRegular);
1350   }
1351 
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);
1360 
1361   nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel =
1362       do_QueryInterface(inputChannel);
1363   if (pbChannel) {
1364     pbChannel->SetPrivate(aIsPrivate);
1365   }
1366 
1367   if (NS_FAILED(rv) || inputChannel == nullptr) {
1368     EndDownload(NS_ERROR_FAILURE);
1369     return NS_ERROR_FAILURE;
1370   }
1371 
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);
1377     }
1378   }
1379 
1380   // Set the referrer, post data and headers if any
1381   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
1382   if (httpChannel) {
1383     if (aReferrerInfo) {
1384       DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
1385       MOZ_ASSERT(NS_SUCCEEDED(success));
1386     }
1387 
1388     // Post data
1389     if (aPostData) {
1390       nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData));
1391       if (stream) {
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);
1399       }
1400     }
1401 
1402     // Cache key
1403     nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
1404     if (cacheChannel && aCacheKey != 0) {
1405       cacheChannel->SetCacheKey(aCacheKey);
1406     }
1407 
1408     // Headers
1409     if (aExtraHeaders) {
1410       nsAutoCString oneHeader;
1411       nsAutoCString headerName;
1412       nsAutoCString headerValue;
1413       int32_t crlf = 0;
1414       int32_t colon = 0;
1415       const char* kWhitespace = "\b\t\r\n ";
1416       nsAutoCString extraHeaders(aExtraHeaders);
1417       while (true) {
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);
1425         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;
1434         }
1435       }
1436     }
1437   }
1438   return SaveChannelInternal(inputChannel, aFile, aCalcFileExt);
1439 }
1440 
SaveChannelInternal(nsIChannel * aChannel,nsIURI * aFile,bool aCalcFileExt)1441 nsresult nsWebBrowserPersist::SaveChannelInternal(nsIChannel* aChannel,
1442                                                   nsIURI* aFile,
1443                                                   bool aCalcFileExt) {
1444   NS_ENSURE_ARG_POINTER(aChannel);
1445   NS_ENSURE_ARG_POINTER(aFile);
1446 
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));
1454 
1455   if (fc && !fu) {
1456     nsCOMPtr<nsIInputStream> fileInputStream, bufferedInputStream;
1457     nsresult rv =
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);
1467   }
1468 
1469   // Mark save channel as throttleable.
1470   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
1471   if (cos) {
1472     cos->AddClassFlags(nsIClassOfService::Throttleable);
1473   }
1474 
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;
1481   }
1482 
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;
1489     }
1490     return NS_SUCCESS_DONT_FIXUP;
1491   }
1492 
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));
1498 
1499   return NS_OK;
1500 }
1501 
GetExtensionForContentType(const char16_t * aContentType,char16_t ** aExt)1502 nsresult nsWebBrowserPersist::GetExtensionForContentType(
1503     const char16_t* aContentType, char16_t** aExt) {
1504   NS_ENSURE_ARG_POINTER(aContentType);
1505   NS_ENSURE_ARG_POINTER(aExt);
1506 
1507   *aExt = nullptr;
1508 
1509   nsresult rv;
1510   if (!mMIMEService) {
1511     mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
1512     NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
1513   }
1514 
1515   nsAutoCString contentType;
1516   LossyCopyUTF16toASCII(MakeStringSpan(aContentType), contentType);
1517   nsAutoCString ext;
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);
1522     return NS_OK;
1523   }
1524 
1525   return NS_ERROR_FAILURE;
1526 }
1527 
SaveDocumentDeferred(mozilla::UniquePtr<WalkData> && aData)1528 nsresult nsWebBrowserPersist::SaveDocumentDeferred(
1529     mozilla::UniquePtr<WalkData>&& aData) {
1530   nsresult rv =
1531       SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath);
1532   if (NS_FAILED(rv)) {
1533     SendErrorStatusChange(true, rv, nullptr, mURI);
1534     EndDownload(rv);
1535   }
1536   return rv;
1537 }
1538 
SaveDocumentInternal(nsIWebBrowserPersistDocument * aDocument,nsIURI * aFile,nsIURI * aDataPath)1539 nsresult nsWebBrowserPersist::SaveDocumentInternal(
1540     nsIWebBrowserPersistDocument* aDocument, nsIURI* aFile, nsIURI* aDataPath) {
1541   mURI = nullptr;
1542   NS_ENSURE_ARG_POINTER(aDocument);
1543   NS_ENSURE_ARG_POINTER(aFile);
1544 
1545   nsresult rv = aDocument->SetPersistFlags(mPersistFlags);
1546   NS_ENSURE_SUCCESS(rv, rv);
1547 
1548   rv = aDocument->GetIsPrivate(&mIsPrivate);
1549   NS_ENSURE_SUCCESS(rv, rv);
1550 
1551   // See if we can get the local file representation of this URI
1552   nsCOMPtr<nsIFile> localFile;
1553   rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
1554 
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);
1560   }
1561 
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);
1575 
1576   // Does the caller want to fixup the referenced URIs and save those too?
1577   if (aDataPath) {
1578     // Basic steps are these.
1579     //
1580     // 1. Iterate through the document (and subdocuments) building a list
1581     //    of unique URIs.
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.
1588 
1589     mCurrentDataPathIsRelative = false;
1590     mCurrentDataPath = aDataPath;
1591     mCurrentRelativePathToData = "";
1592     mCurrentThingsToPersist = 0;
1593     mTargetBaseURI = aFile;
1594 
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.
1598 
1599     // Starting with the data dir work back through its parents
1600     // checking if one of them matches the base directory.
1601 
1602     if (localDataPath && localFile) {
1603       nsCOMPtr<nsIFile> baseDir;
1604       localFile->GetParent(getter_AddRefs(baseDir));
1605 
1606       nsAutoCString relativePathToData;
1607       nsCOMPtr<nsIFile> dataDirParent;
1608       dataDirParent = localDataPath;
1609       while (dataDirParent) {
1610         bool sameDir = false;
1611         dataDirParent->Equals(baseDir, &sameDir);
1612         if (sameDir) {
1613           mCurrentRelativePathToData = relativePathToData;
1614           mCurrentDataPathIsRelative = true;
1615           break;
1616         }
1617 
1618         nsAutoString dirName;
1619         dataDirParent->GetLeafName(dirName);
1620 
1621         nsAutoCString newRelativePathToData;
1622         newRelativePathToData =
1623             NS_ConvertUTF16toUTF8(dirName) + "/"_ns + relativePathToData;
1624         relativePathToData = newRelativePathToData;
1625 
1626         nsCOMPtr<nsIFile> newDataDirParent;
1627         rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent));
1628         dataDirParent = newDataDirParent;
1629       }
1630     } else {
1631       // generate a relative path if possible
1632       nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile));
1633       if (pathToBaseURL) {
1634         nsAutoCString relativePath;  // nsACString
1635         if (NS_SUCCEEDED(
1636                 pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) {
1637           mCurrentDataPathIsRelative = true;
1638           mCurrentRelativePathToData = relativePath;
1639         }
1640       }
1641     }
1642 
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
1645     // saved
1646 
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);
1653 
1654     // Walk the DOM gathering a list of externally referenced URIs in the uri
1655     // map
1656     nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit =
1657         new OnWalk(this, aFile, localDataPath);
1658     return aDocument->ReadResources(visit);
1659   } else {
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);
1666 
1667     // Not walking DOMs, so go directly to serialization.
1668     SerializeNextFile();
1669     return NS_OK;
1670   }
1671 }
1672 
1673 NS_IMETHODIMP
VisitResource(nsIWebBrowserPersistDocument * aDoc,const nsACString & aURI,nsContentPolicyType aContentPolicyType)1674 nsWebBrowserPersist::OnWalk::VisitResource(
1675     nsIWebBrowserPersistDocument* aDoc, const nsACString& aURI,
1676     nsContentPolicyType aContentPolicyType) {
1677   return mParent->StoreURI(aURI, aDoc, aContentPolicyType);
1678 }
1679 
1680 NS_IMETHODIMP
VisitDocument(nsIWebBrowserPersistDocument * aDoc,nsIWebBrowserPersistDocument * aSubDoc)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,
1688                          false, &data);
1689   NS_ENSURE_SUCCESS(rv, rv);
1690   if (!data) {
1691     // If the URI scheme isn't persistable, then don't persist.
1692     return NS_OK;
1693   }
1694   data->mIsSubFrame = true;
1695   return mParent->SaveSubframeContent(aSubDoc, aDoc, uriSpec, data);
1696 }
1697 
1698 NS_IMETHODIMP
VisitBrowsingContext(nsIWebBrowserPersistDocument * aDoc,BrowsingContext * aContext)1699 nsWebBrowserPersist::OnWalk::VisitBrowsingContext(
1700     nsIWebBrowserPersistDocument* aDoc, BrowsingContext* aContext) {
1701   RefPtr<dom::CanonicalBrowsingContext> context = aContext->Canonical();
1702 
1703   if (NS_WARN_IF(!context->GetCurrentWindowGlobal())) {
1704     EndVisit(nullptr, NS_ERROR_FAILURE);
1705     return NS_ERROR_FAILURE;
1706   }
1707 
1708   UniquePtr<WebBrowserPersistDocumentParent> actor(
1709       new WebBrowserPersistDocumentParent());
1710 
1711   nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> receiver =
1712       new OnRemoteWalk(this, aDoc);
1713   actor->SetOnReady(receiver);
1714 
1715   RefPtr<dom::BrowserParent> browserParent =
1716       context->GetCurrentWindowGlobal()->GetBrowserParent();
1717 
1718   bool ok =
1719       context->GetContentParent()->SendPWebBrowserPersistDocumentConstructor(
1720           actor.release(), browserParent, context);
1721 
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;
1726   }
1727 
1728   ++mPendingDocuments;
1729 
1730   return NS_OK;
1731 }
1732 
1733 NS_IMETHODIMP
EndVisit(nsIWebBrowserPersistDocument * aDoc,nsresult aStatus)1734 nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
1735                                       nsresult aStatus) {
1736   if (NS_FAILED(mStatus)) {
1737     return mStatus;
1738   }
1739 
1740   if (NS_FAILED(aStatus)) {
1741     mStatus = aStatus;
1742     mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
1743     mParent->EndDownload(aStatus);
1744     return aStatus;
1745   }
1746 
1747   if (--mPendingDocuments) {
1748     // We're not done yet, wait for more.
1749     return NS_OK;
1750   }
1751 
1752   mParent->FinishSaveDocumentInternal(mFile, mDataPath);
1753   return NS_OK;
1754 }
1755 
1756 NS_IMETHODIMP
OnDocumentReady(nsIWebBrowserPersistDocument * aSubDocument)1757 nsWebBrowserPersist::OnRemoteWalk::OnDocumentReady(
1758     nsIWebBrowserPersistDocument* aSubDocument) {
1759   mVisitor->VisitDocument(mDocument, aSubDocument);
1760   mVisitor->EndVisit(mDocument, NS_OK);
1761   return NS_OK;
1762 }
1763 
1764 NS_IMETHODIMP
OnError(nsresult aFailure)1765 nsWebBrowserPersist::OnRemoteWalk::OnError(nsresult aFailure) {
1766   mVisitor->EndVisit(nullptr, aFailure);
1767   return NS_OK;
1768 }
1769 
FinishSaveDocumentInternal(nsIURI * aFile,nsIFile * aDataPath)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) {
1774     if (aDataPath) {
1775       bool exists = false;
1776       bool haveDir = false;
1777 
1778       aDataPath->Exists(&exists);
1779       if (exists) {
1780         aDataPath->IsDirectory(&haveDir);
1781       }
1782       if (!haveDir) {
1783         nsresult rv = aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
1784         if (NS_SUCCEEDED(rv)) {
1785           haveDir = true;
1786         } else {
1787           SendErrorStatusChange(false, rv, nullptr, aFile);
1788         }
1789       }
1790       if (!haveDir) {
1791         EndDownload(NS_ERROR_FAILURE);
1792         return;
1793       }
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);
1800       }
1801     }
1802   }
1803 
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,
1811         std::move(toWalk));
1812     NS_DispatchToCurrentThread(saveLater);
1813   } else {
1814     // Done walking DOMs; on to the serialization phase.
1815     SerializeNextFile();
1816   }
1817 }
1818 
Cleanup()1819 void nsWebBrowserPersist::Cleanup() {
1820   mURIMap.Clear();
1821   nsClassHashtable<nsISupportsHashKey, OutputData> outputMapCopy;
1822   {
1823     MutexAutoLock lock(mOutputMapMutex);
1824     mOutputMap.SwapElements(outputMapCopy);
1825   }
1826   for (const auto& key : outputMapCopy.Keys()) {
1827     nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
1828     if (channel) {
1829       channel->Cancel(NS_BINDING_ABORTED);
1830     }
1831   }
1832   outputMapCopy.Clear();
1833 
1834   for (const auto& key : mUploadList.Keys()) {
1835     nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
1836     if (channel) {
1837       channel->Cancel(NS_BINDING_ABORTED);
1838     }
1839   }
1840   mUploadList.Clear();
1841 
1842   uint32_t i;
1843   for (i = 0; i < mDocList.Length(); i++) {
1844     DocData* docData = mDocList.ElementAt(i);
1845     delete docData;
1846   }
1847   mDocList.Clear();
1848 
1849   for (i = 0; i < mCleanupList.Length(); i++) {
1850     CleanupData* cleanupData = mCleanupList.ElementAt(i);
1851     delete cleanupData;
1852   }
1853   mCleanupList.Clear();
1854 
1855   mFilenameList.Clear();
1856 }
1857 
CleanupLocalFiles()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.
1863   int pass;
1864   for (pass = 0; pass < 2; pass++) {
1865     uint32_t i;
1866     for (i = 0; i < mCleanupList.Length(); i++) {
1867       CleanupData* cleanupData = mCleanupList.ElementAt(i);
1868       nsCOMPtr<nsIFile> file = cleanupData->mFile;
1869 
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;
1875 
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 !
1882 
1883       if (pass == 0 && !isDirectory) {
1884         file->Remove(false);
1885       } else if (pass == 1 && isDirectory)  // Directory
1886       {
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.
1894 
1895         bool isEmptyDirectory = true;
1896         nsCOMArray<nsIDirectoryEnumerator> dirStack;
1897         int32_t stackSize = 0;
1898 
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);
1903 
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);
1909 
1910           nsCOMPtr<nsIFile> child;
1911           if (NS_FAILED(curPos->GetNextFile(getter_AddRefs(child))) || !child) {
1912             continue;
1913           }
1914 
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;
1923             break;
1924           }
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);
1930         }
1931         dirStack.Clear();
1932 
1933         // If after all that walking the dir is deemed empty, delete it
1934         if (isEmptyDirectory) {
1935           file->Remove(true);
1936         }
1937       }
1938     }
1939   }
1940 }
1941 
CalculateUniqueFilename(nsIURI * aURI,nsCOMPtr<nsIURI> & aOutURI)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);
1946 
1947   bool nameHasChanged = false;
1948   nsresult rv;
1949 
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);
1957 
1958   // Split the filename into a base and an extension.
1959   // e.g. "foo.html" becomes "foo" & ".html"
1960   //
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(".");
1965   nsAutoCString base;
1966   nsAutoCString ext;
1967   if (lastDot >= 0) {
1968     filename.Mid(base, 0, lastDot);
1969     filename.Mid(ext, lastDot, filename.Length() - lastDot);  // includes dot
1970   } else {
1971     // filename contains no dot
1972     base = filename;
1973   }
1974 
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);
1981     } else {
1982       needToChop -= base.Length() - 1;
1983       base.Truncate(1);
1984       if (ext.Length() > (uint32_t)needToChop) {
1985         ext.Truncate(ext.Length() - needToChop);
1986       } else {
1987         ext.Truncate(0);
1988       }
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
1991       // value.
1992     }
1993 
1994     filename.Assign(base);
1995     filename.Append(ext);
1996     nameHasChanged = true;
1997   }
1998 
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.
2002 
2003   if (base.IsEmpty() || !mFilenameList.IsEmpty()) {
2004     nsAutoCString tmpPath;
2005     nsAutoCString tmpBase;
2006     uint32_t duplicateCounter = 1;
2007     while (true) {
2008       // Make a file name,
2009       // Foo become foo_001, foo_002, etc.
2010       // Empty files become _001, _002 etc.
2011 
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) {
2016           tmpBase = base;
2017         } else {
2018           base.Mid(tmpBase, 0, base.Length() - 4);
2019         }
2020         tmpBase.Append(tmp.get());
2021       } else {
2022         tmpBase = base;
2023       }
2024 
2025       tmpPath.Assign(directory);
2026       tmpPath.Append(tmpBase);
2027       tmpPath.Append(ext);
2028 
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;
2035         }
2036         break;
2037       }
2038       duplicateCounter++;
2039     }
2040   }
2041 
2042   // Add name to list of those already used
2043   nsAutoCString newFilepath(directory);
2044   newFilepath.Append(filename);
2045   mFilenameList.AppendElement(newFilepath);
2046 
2047   // Update the uri accordingly if the filename actually changed
2048   if (nameHasChanged) {
2049     // Final sanity test
2050     if (filename.Length() > kDefaultMaxFilenameLength) {
2051       NS_WARNING(
2052           "Filename wasn't truncated less than the max file length - how can "
2053           "that be?");
2054       return NS_ERROR_FAILURE;
2055     }
2056 
2057     nsCOMPtr<nsIFile> localFile;
2058     GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
2059 
2060     if (localFile) {
2061       nsAutoString filenameAsUnichar;
2062       CopyASCIItoUTF16(filename, filenameAsUnichar);
2063       localFile->SetLeafName(filenameAsUnichar);
2064 
2065       // Resync the URI with the file after the extension has been appended
2066       return NS_MutateURI(aURI)
2067           .Apply(&nsIFileURLMutator::SetFile, localFile)
2068           .Finalize(aOutURI);
2069     }
2070     return NS_MutateURI(url)
2071         .Apply(&nsIURLMutator::SetFileName, filename, nullptr)
2072         .Finalize(aOutURI);
2073   }
2074 
2075   // TODO (:valentin) This method should always clone aURI
2076   aOutURI = aURI;
2077   return NS_OK;
2078 }
2079 
MakeFilenameFromURI(nsIURI * aURI,nsString & aFilename)2080 nsresult nsWebBrowserPersist::MakeFilenameFromURI(nsIURI* aURI,
2081                                                   nsString& aFilename) {
2082   // Try to get filename from the URI.
2083   nsAutoString fileName;
2084 
2085   // Get a suggested file name from the URL but strip it of characters
2086   // likely to cause the name to be illegal.
2087 
2088   nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
2089   if (url) {
2090     nsAutoCString nameFromURL;
2091     url->GetFileName(nameFromURL);
2092     if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) {
2093       CopyASCIItoUTF16(NS_UnescapeURL(nameFromURL), fileName);
2094       aFilename = fileName;
2095       return NS_OK;
2096     }
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) {
2107             // Note:
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.
2114             break;
2115           }
2116         }
2117       }
2118     }
2119   }
2120 
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
2127   }
2128 
2129   aFilename = fileName;
2130   return NS_OK;
2131 }
2132 
CalculateAndAppendFileExt(nsIURI * aURI,nsIChannel * aChannel,nsIURI * aOriginalURIWithExtension,nsCOMPtr<nsIURI> & aOutURI)2133 nsresult nsWebBrowserPersist::CalculateAndAppendFileExt(
2134     nsIURI* aURI, nsIChannel* aChannel, nsIURI* aOriginalURIWithExtension,
2135     nsCOMPtr<nsIURI>& aOutURI) {
2136   nsresult rv = NS_OK;
2137 
2138   if (!mMIMEService) {
2139     mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
2140     NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
2141   }
2142 
2143   nsAutoCString contentType;
2144 
2145   // Get the content type from the channel
2146   aChannel->GetContentType(contentType);
2147 
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);
2153   }
2154 
2155   // Append the extension onto the file
2156   if (!contentType.IsEmpty()) {
2157     nsCOMPtr<nsIMIMEInfo> mimeInfo;
2158     mMIMEService->GetFromTypeAndExtension(contentType, ""_ns,
2159                                           getter_AddRefs(mimeInfo));
2160 
2161     nsCOMPtr<nsIFile> localFile;
2162     GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
2163 
2164     if (mimeInfo) {
2165       nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
2166       NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
2167 
2168       nsAutoCString newFileName;
2169       url->GetFileName(newFileName);
2170 
2171       // Test if the current extension is current for the mime type
2172       bool hasExtension = false;
2173       int32_t ext = newFileName.RFind(".");
2174       if (ext != -1) {
2175         mimeInfo->ExtensionExists(Substring(newFileName, ext + 1),
2176                                   &hasExtension);
2177       }
2178 
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);
2189         }
2190 
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.
2194         if (!useOldExt) {
2195           nsAutoCString primaryExt;
2196           mimeInfo->GetPrimaryExtension(primaryExt);
2197           if (!primaryExt.IsEmpty()) {
2198             fileExt = primaryExt;
2199           }
2200         }
2201 
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);
2207 
2208             uint32_t diff = kDefaultMaxFilenameLength - 1 - fileExt.Length();
2209             if (newFileName.Length() > diff) newFileName.Truncate(diff);
2210           }
2211           newFileName.Append('.');
2212           newFileName.Append(fileExt);
2213         }
2214 
2215         if (localFile) {
2216           localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName));
2217 
2218           // Resync the URI with the file after the extension has been appended
2219           return NS_MutateURI(url)
2220               .Apply(&nsIFileURLMutator::SetFile, localFile)
2221               .Finalize(aOutURI);
2222         }
2223         return NS_MutateURI(url)
2224             .Apply(&nsIURLMutator::SetFileName, newFileName, nullptr)
2225             .Finalize(aOutURI);
2226       }
2227     }
2228   }
2229 
2230   // TODO (:valentin) This method should always clone aURI
2231   aOutURI = aURI;
2232   return NS_OK;
2233 }
2234 
2235 // Note: the MakeOutputStream helpers can be called from a background thread.
MakeOutputStream(nsIURI * aURI,nsIOutputStream ** aOutputStream)2236 nsresult nsWebBrowserPersist::MakeOutputStream(
2237     nsIURI* aURI, nsIOutputStream** aOutputStream) {
2238   nsresult rv;
2239 
2240   nsCOMPtr<nsIFile> localFile;
2241   GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
2242   if (localFile)
2243     rv = MakeOutputStreamFromFile(localFile, aOutputStream);
2244   else
2245     rv = MakeOutputStreamFromURI(aURI, aOutputStream);
2246 
2247   return rv;
2248 }
2249 
MakeOutputStreamFromFile(nsIFile * aFile,nsIOutputStream ** aOutputStream)2250 nsresult nsWebBrowserPersist::MakeOutputStreamFromFile(
2251     nsIFile* aFile, nsIOutputStream** aOutputStream) {
2252   nsresult rv = NS_OK;
2253 
2254   nsCOMPtr<nsIFileOutputStream> fileOutputStream =
2255       do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
2256   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
2257 
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);
2264 
2265   rv = NS_NewBufferedOutputStream(aOutputStream, fileOutputStream.forget(),
2266                                   BUFFERED_OUTPUT_SIZE);
2267   NS_ENSURE_SUCCESS(rv, rv);
2268 
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);
2276     } else {
2277       // If we're on a background thread, add the cleanup back on the main
2278       // thread.
2279       RefPtr<Runnable> addCleanup = NS_NewRunnableFunction(
2280           "nsWebBrowserPersist::AddCleanupToList",
2281           [self = RefPtr{this}, cleanup = std::move(cleanupData)]() {
2282             self->mCleanupList.AppendElement(cleanup);
2283           });
2284       NS_DispatchToMainThread(addCleanup);
2285     }
2286   }
2287 
2288   return NS_OK;
2289 }
2290 
MakeOutputStreamFromURI(nsIURI * aURI,nsIOutputStream ** aOutputStream)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;
2296   nsresult rv =
2297       NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream));
2298   NS_ENSURE_SUCCESS(rv, rv);
2299 
2300   NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream),
2301                     NS_ERROR_FAILURE);
2302   return NS_OK;
2303 }
2304 
FinishDownload()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.
2311   if (mEndCalled) {
2312     return;
2313   }
2314   EndDownload(NS_OK);
2315 }
2316 
EndDownload(nsresult aResult)2317 void nsWebBrowserPersist::EndDownload(nsresult aResult) {
2318   MOZ_ASSERT(NS_IsMainThread(), "Should end download on the main thread.");
2319 
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)) {
2323     return;
2324   }
2325 
2326   // Store the error code in the result if it is an error
2327   if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) {
2328     mPersistResult = aResult;
2329   }
2330 
2331   if (mEndCalled) {
2332     MOZ_ASSERT(!mEndCalled, "Should only end the download once.");
2333     return;
2334   }
2335   mEndCalled = true;
2336 
2337   ClosePromise::All(GetCurrentSerialEventTarget(), mFileClosePromises)
2338       ->Then(GetCurrentSerialEventTarget(), __func__,
2339              [self = RefPtr{this}, aResult]() {
2340                self->EndDownloadInternal(aResult);
2341              });
2342 }
2343 
EndDownloadInternal(nsresult aResult)2344 void nsWebBrowserPersist::EndDownloadInternal(nsresult aResult) {
2345   // mCompleted needs to be set before issuing the stop notification.
2346   // (Bug 1224437)
2347   mCompleted = true;
2348   // State stop notification
2349   if (mProgressListener) {
2350     mProgressListener->OnStateChange(
2351         nullptr, nullptr,
2352         nsIWebProgressListener::STATE_STOP |
2353             nsIWebProgressListener::STATE_IS_NETWORK,
2354         mPersistResult);
2355   }
2356 
2357   // Do file cleanup if required
2358   if (NS_FAILED(aResult) &&
2359       (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) {
2360     CleanupLocalFiles();
2361   }
2362 
2363   // Cleanup the channels
2364   Cleanup();
2365 
2366   mProgressListener = nullptr;
2367   mProgressListener2 = nullptr;
2368   mEventSink = nullptr;
2369 }
2370 
FixRedirectedChannelEntry(nsIChannel * aNewChannel)2371 nsresult nsWebBrowserPersist::FixRedirectedChannelEntry(
2372     nsIChannel* aNewChannel) {
2373   NS_ENSURE_ARG_POINTER(aNewChannel);
2374 
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;
2383 
2384     thisChannel->GetOriginalURI(getter_AddRefs(thisURI));
2385 
2386     // Compare this channel's URI to the one passed in.
2387     bool matchingURI = false;
2388     thisURI->Equals(originalURI, &matchingURI);
2389     if (matchingURI) {
2390       matchingKey = key;
2391       break;
2392     }
2393   }
2394 
2395   if (matchingKey) {
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);
2404 
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));
2409     }
2410   }
2411 
2412   return NS_OK;
2413 }
2414 
CalcTotalProgress()2415 void nsWebBrowserPersist::CalcTotalProgress() {
2416   mTotalCurrentProgress = 0;
2417   mTotalMaxProgress = 0;
2418 
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);
2424       if (fileURL) {
2425         mTotalCurrentProgress += data->mSelfProgress;
2426         mTotalMaxProgress += data->mSelfProgressMax;
2427       }
2428     }
2429   }
2430 
2431   if (mUploadList.Count() > 0) {
2432     // Total up the progress of each upload
2433     for (const auto& data : mUploadList.Values()) {
2434       if (data) {
2435         mTotalCurrentProgress += data->mSelfProgress;
2436         mTotalMaxProgress += data->mSelfProgressMax;
2437       }
2438     }
2439   }
2440 
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;
2446   }
2447 }
2448 
StoreURI(const nsACString & aURI,nsIWebBrowserPersistDocument * aDoc,nsContentPolicyType aContentPolicyType,bool aNeedsPersisting,URIData ** aData)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(),
2455                           mCurrentBaseURI);
2456   NS_ENSURE_SUCCESS(rv, rv);
2457 
2458   return StoreURI(uri, aDoc, aContentPolicyType, aNeedsPersisting, aData);
2459 }
2460 
StoreURI(nsIURI * aURI,nsIWebBrowserPersistDocument * aDoc,nsContentPolicyType aContentPolicyType,bool aNeedsPersisting,URIData ** aData)2461 nsresult nsWebBrowserPersist::StoreURI(nsIURI* aURI,
2462                                        nsIWebBrowserPersistDocument* aDoc,
2463                                        nsContentPolicyType aContentPolicyType,
2464                                        bool aNeedsPersisting, URIData** aData) {
2465   NS_ENSURE_ARG_POINTER(aURI);
2466   if (aData) {
2467     *aData = nullptr;
2468   }
2469 
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;
2477   }
2478 
2479   if (doNotPersistURI) {
2480     return NS_OK;
2481   }
2482 
2483   URIData* data = nullptr;
2484   MakeAndStoreLocalFilenameInURIMap(aURI, aDoc, aContentPolicyType,
2485                                     aNeedsPersisting, &data);
2486   if (aData) {
2487     *aData = data;
2488   }
2489 
2490   return NS_OK;
2491 }
2492 
GetLocalURI(nsIURI * targetBaseURI,nsCString & aSpecOut)2493 nsresult nsWebBrowserPersist::URIData::GetLocalURI(nsIURI* targetBaseURI,
2494                                                    nsCString& aSpecOut) {
2495   aSpecOut.SetIsVoid(true);
2496   if (!mNeedsFixup) {
2497     return NS_OK;
2498   }
2499   nsresult rv;
2500   nsCOMPtr<nsIURI> fileAsURI;
2501   if (mFile) {
2502     fileAsURI = mFile;
2503   } else {
2504     fileAsURI = mDataPath;
2505     rv = AppendPathToURI(fileAsURI, mFilename, fileAsURI);
2506     NS_ENSURE_SUCCESS(rv, rv);
2507   }
2508 
2509   // remove username/password if present
2510   Unused << NS_MutateURI(fileAsURI).SetUserPass(""_ns).Finalize(fileAsURI);
2511 
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)) &&
2517         isEqual) {
2518       nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI));
2519       if (!url) {
2520         return NS_ERROR_FAILURE;
2521       }
2522 
2523       nsAutoCString filename;
2524       url->GetFileName(filename);
2525 
2526       nsAutoCString rawPathURL(mRelativePathToData);
2527       rawPathURL.Append(filename);
2528 
2529       rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible);
2530       NS_ENSURE_SUCCESS(rv, rv);
2531     } else {
2532       nsAutoCString rawPathURL;
2533 
2534       nsCOMPtr<nsIFile> dataFile;
2535       rv = GetLocalFileFromURI(mFile, getter_AddRefs(dataFile));
2536       NS_ENSURE_SUCCESS(rv, rv);
2537 
2538       nsCOMPtr<nsIFile> docFile;
2539       rv = GetLocalFileFromURI(targetBaseURI, getter_AddRefs(docFile));
2540       NS_ENSURE_SUCCESS(rv, rv);
2541 
2542       nsCOMPtr<nsIFile> parentDir;
2543       rv = docFile->GetParent(getter_AddRefs(parentDir));
2544       NS_ENSURE_SUCCESS(rv, rv);
2545 
2546       rv = dataFile->GetRelativePath(parentDir, rawPathURL);
2547       NS_ENSURE_SUCCESS(rv, rv);
2548 
2549       rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible);
2550       NS_ENSURE_SUCCESS(rv, rv);
2551     }
2552   } else {
2553     fileAsURI->GetSpec(aSpecOut);
2554   }
2555   if (mIsSubFrame) {
2556     AppendUTF16toUTF8(mSubFrameExt, aSpecOut);
2557   }
2558 
2559   return NS_OK;
2560 }
2561 
DocumentEncoderExists(const char * aContentType)2562 bool nsWebBrowserPersist::DocumentEncoderExists(const char* aContentType) {
2563   return do_getDocumentTypeSupportedForEncoding(aContentType);
2564 }
2565 
SaveSubframeContent(nsIWebBrowserPersistDocument * aFrameContent,nsIWebBrowserPersistDocument * aParentDocument,const nsCString & aURISpec,URIData * aData)2566 nsresult nsWebBrowserPersist::SaveSubframeContent(
2567     nsIWebBrowserPersistDocument* aFrameContent,
2568     nsIWebBrowserPersistDocument* aParentDocument, const nsCString& aURISpec,
2569     URIData* aData) {
2570   NS_ENSURE_ARG_POINTER(aData);
2571 
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);
2576 
2577   nsString ext;
2578   GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(),
2579                              getter_Copies(ext));
2580 
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);
2587 
2588     nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv));
2589     nsAutoCString extension;
2590     if (NS_SUCCEEDED(rv)) {
2591       url->GetFileExtension(extension);
2592     } else {
2593       extension.AssignLiteral("htm");
2594     }
2595     aData->mSubFrameExt.Assign(char16_t('.'));
2596     AppendUTF8toUTF16(extension, aData->mSubFrameExt);
2597   } else {
2598     aData->mSubFrameExt.Assign(char16_t('.'));
2599     aData->mSubFrameExt.Append(ext);
2600   }
2601 
2602   nsString filenameWithExt = aData->mFilename;
2603   filenameWithExt.Append(aData->mSubFrameExt);
2604 
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);
2609 
2610   // Work out the path for the subframe data
2611   nsCOMPtr<nsIURI> frameDataURI = mCurrentDataPath;
2612   nsAutoString newFrameDataPath(aData->mFilename);
2613 
2614   // Append _data
2615   newFrameDataPath.AppendLiteral("_data");
2616   rv = AppendPathToURI(frameDataURI, newFrameDataPath, frameDataURI);
2617   NS_ENSURE_SUCCESS(rv, rv);
2618 
2619   // Make frame document & data path conformant and unique
2620   nsCOMPtr<nsIURI> out;
2621   rv = CalculateUniqueFilename(frameURI, out);
2622   NS_ENSURE_SUCCESS(rv, rv);
2623   frameURI = out;
2624 
2625   rv = CalculateUniqueFilename(frameDataURI, out);
2626   NS_ENSURE_SUCCESS(rv, rv);
2627   frameDataURI = out;
2628 
2629   mCurrentThingsToPersist++;
2630 
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));
2639   } else {
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;
2646     }
2647     rv = StoreURI(aURISpec, aParentDocument, policyType);
2648   }
2649   NS_ENSURE_SUCCESS(rv, rv);
2650 
2651   // Store the updated uri to the frame
2652   aData->mFile = frameURI;
2653   aData->mSubFrameExt.Truncate();  // we already put this in frameURI
2654 
2655   return NS_OK;
2656 }
2657 
CreateChannelFromURI(nsIURI * aURI,nsIChannel ** aChannel)2658 nsresult nsWebBrowserPersist::CreateChannelFromURI(nsIURI* aURI,
2659                                                    nsIChannel** aChannel) {
2660   nsresult rv = NS_OK;
2661   *aChannel = nullptr;
2662 
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);
2668 
2669   rv = (*aChannel)->SetNotificationCallbacks(
2670       static_cast<nsIInterfaceRequestor*>(this));
2671   NS_ENSURE_SUCCESS(rv, rv);
2672   return NS_OK;
2673 }
2674 
2675 // we store the current location as the key (absolutized version of domnode's
2676 // attribute's value)
MakeAndStoreLocalFilenameInURIMap(nsIURI * aURI,nsIWebBrowserPersistDocument * aDoc,nsContentPolicyType aContentPolicyType,bool aNeedsPersisting,URIData ** aData)2677 nsresult nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap(
2678     nsIURI* aURI, nsIWebBrowserPersistDocument* aDoc,
2679     nsContentPolicyType aContentPolicyType, bool aNeedsPersisting,
2680     URIData** aData) {
2681   NS_ENSURE_ARG_POINTER(aURI);
2682 
2683   nsAutoCString spec;
2684   nsresult rv = aURI->GetSpec(spec);
2685   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
2686 
2687   // Create a sensibly named filename for the URI and store in the URI map
2688   URIData* data;
2689   if (mURIMap.Get(spec, &data)) {
2690     if (aNeedsPersisting) {
2691       data->mNeedsPersisting = true;
2692     }
2693     if (aData) {
2694       *aData = data;
2695     }
2696     return NS_OK;
2697   }
2698 
2699   // Create a unique file name for the uri
2700   nsString filename;
2701   rv = MakeFilenameFromURI(aURI, filename);
2702   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
2703 
2704   // Store the file name
2705   data = new URIData;
2706 
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;
2718 
2719   aDoc->GetPrincipal(getter_AddRefs(data->mTriggeringPrincipal));
2720   aDoc->GetCookieJarSettings(getter_AddRefs(data->mCookieJarSettings));
2721 
2722   if (aNeedsPersisting) mCurrentThingsToPersist++;
2723 
2724   mURIMap.InsertOrUpdate(spec, UniquePtr<URIData>(data));
2725   if (aData) {
2726     *aData = data;
2727   }
2728 
2729   return NS_OK;
2730 }
2731 
2732 // Decide if we need to apply conversion to the passed channel.
SetApplyConversionIfNeeded(nsIChannel * aChannel)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;
2737 
2738   // Set the default conversion preference:
2739   encChannel->SetApplyConversion(false);
2740 
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);
2747 
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;
2754   bool hasMore;
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,
2762                                                        &applyConversion);
2763       if (NS_SUCCEEDED(rv)) encChannel->SetApplyConversion(applyConversion);
2764     }
2765   }
2766 }
2767