tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsWebBrowserPersist.cpp (90672B)


      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 "nsWebBrowserPersist.h"
      7 
      8 #include <algorithm>
      9 
     10 #include "ReferrerInfo.h"
     11 #include "WebBrowserPersistLocalDocument.h"
     12 #include "mozilla/Mutex.h"
     13 #include "mozilla/Printf.h"
     14 #include "mozilla/TextUtils.h"
     15 #include "mozilla/WebBrowserPersistDocumentParent.h"
     16 #include "mozilla/dom/BrowserParent.h"
     17 #include "mozilla/dom/CanonicalBrowsingContext.h"
     18 #include "mozilla/dom/ContentParent.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "mozilla/dom/PContentParent.h"
     21 #include "mozilla/dom/WindowGlobalParent.h"
     22 #include "mozilla/net/CookieJarSettings.h"
     23 #include "nsCExternalHandlerService.h"
     24 #include "nsComponentManagerUtils.h"
     25 #include "nsContentUtils.h"
     26 #include "nsEscape.h"
     27 #include "nsIAuthPrompt.h"
     28 #include "nsICacheInfoChannel.h"
     29 #include "nsIClassOfService.h"
     30 #include "nsIContent.h"
     31 #include "nsIDocumentEncoder.h"
     32 #include "nsIEncodedChannel.h"
     33 #include "nsIFileChannel.h"
     34 #include "nsIFileStreams.h"  // New Necko file streams
     35 #include "nsIFileURL.h"
     36 #include "nsIHttpChannel.h"
     37 #include "nsIInterfaceRequestorUtils.h"
     38 #include "nsIMIMEInfo.h"
     39 #include "nsIPrivateBrowsingChannel.h"
     40 #include "nsIPrompt.h"
     41 #include "nsIProtocolHandler.h"
     42 #include "nsISeekableStream.h"
     43 #include "nsIStorageStream.h"
     44 #include "nsIStringBundle.h"
     45 #include "nsIStringEnumerator.h"
     46 #include "nsIThreadRetargetableRequest.h"
     47 #include "nsIURIMutator.h"
     48 #include "nsIURL.h"
     49 #include "nsIUploadChannel.h"
     50 #include "nsIWebProgressListener.h"
     51 #include "nsNetCID.h"
     52 #include "nsNetUtil.h"
     53 #include "nsStreamUtils.h"
     54 #include "nspr.h"
     55 
     56 using namespace mozilla;
     57 using namespace mozilla::dom;
     58 
     59 // Buffer file writes in 32kb chunks
     60 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
     61 
     62 struct nsWebBrowserPersist::WalkData {
     63  nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
     64  nsCOMPtr<nsIURI> mFile;
     65  nsCOMPtr<nsIURI> mDataPath;
     66 };
     67 
     68 // Information about a DOM document
     69 struct nsWebBrowserPersist::DocData {
     70  nsCOMPtr<nsIURI> mBaseURI;
     71  nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
     72  nsCOMPtr<nsIURI> mFile;
     73  nsCString mCharset;
     74 };
     75 
     76 // Information about a URI
     77 struct nsWebBrowserPersist::URIData {
     78  bool mNeedsPersisting;
     79  bool mSaved;
     80  bool mIsSubFrame;
     81  bool mDataPathIsRelative;
     82  bool mNeedsFixup;
     83  nsString mFilename;
     84  nsString mSubFrameExt;
     85  nsCOMPtr<nsIURI> mFile;
     86  nsCOMPtr<nsIURI> mDataPath;
     87  nsCOMPtr<nsIURI> mRelativeDocumentURI;
     88  nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
     89  nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
     90  nsContentPolicyType mContentPolicyType;
     91  nsCString mRelativePathToData;
     92  nsCString mCharset;
     93 
     94  nsresult GetLocalURI(nsIURI* targetBaseURI, nsCString& aSpecOut);
     95 };
     96 
     97 // Information about the output stream
     98 // Note that this data structure (and the map that nsWebBrowserPersist keeps,
     99 // where these are values) is used from two threads: the main thread,
    100 // and the background task thread.
    101 // The background thread only writes to mStream (from OnDataAvailable), and
    102 // this access is guarded using mStreamMutex. It reads the mFile member, which
    103 // is only written to on the main thread when the object is constructed and
    104 // from OnStartRequest (if mCalcFileExt), both guaranteed to happen before
    105 // OnDataAvailable is fired.
    106 // The main thread gets OnStartRequest, OnStopRequest, and progress sink events,
    107 // and accesses the other members.
    108 struct nsWebBrowserPersist::OutputData {
    109  nsCOMPtr<nsIURI> mFile;
    110  nsCOMPtr<nsIURI> mOriginalLocation;
    111  nsCOMPtr<nsIOutputStream> mStream;
    112  Mutex mStreamMutex MOZ_UNANNOTATED;
    113  int64_t mSelfProgress;
    114  int64_t mSelfProgressMax;
    115  bool mCalcFileExt;
    116 
    117  OutputData(nsIURI* aFile, nsIURI* aOriginalLocation, bool aCalcFileExt)
    118      : mFile(aFile),
    119        mOriginalLocation(aOriginalLocation),
    120        mStreamMutex("nsWebBrowserPersist::OutputData::mStreamMutex"),
    121        mSelfProgress(0),
    122        mSelfProgressMax(10000),
    123        mCalcFileExt(aCalcFileExt) {}
    124  ~OutputData() {
    125    // Gaining this lock in the destructor is pretty icky. It should be OK
    126    // because the only other place we lock the mutex is in OnDataAvailable,
    127    // which will never itself cause the OutputData instance to be
    128    // destroyed.
    129    MutexAutoLock lock(mStreamMutex);
    130    if (mStream) {
    131      mStream->Close();
    132    }
    133  }
    134 };
    135 
    136 struct nsWebBrowserPersist::UploadData {
    137  nsCOMPtr<nsIURI> mFile;
    138  int64_t mSelfProgress;
    139  int64_t mSelfProgressMax;
    140 
    141  explicit UploadData(nsIURI* aFile)
    142      : mFile(aFile), mSelfProgress(0), mSelfProgressMax(10000) {}
    143 };
    144 
    145 struct nsWebBrowserPersist::CleanupData {
    146  nsCOMPtr<nsIFile> mFile;
    147  // Snapshot of what the file actually is at the time of creation so that if
    148  // it transmutes into something else later on it can be ignored. For example,
    149  // catch files that turn into dirs or vice versa.
    150  bool mIsDirectory;
    151 };
    152 
    153 class nsWebBrowserPersist::OnWalk final
    154    : public nsIWebBrowserPersistResourceVisitor {
    155 public:
    156  OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath)
    157      : mParent(aParent),
    158        mFile(aFile),
    159        mDataPath(aDataPath),
    160        mPendingDocuments(1),
    161        mStatus(NS_OK) {}
    162 
    163  NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR
    164  NS_DECL_ISUPPORTS
    165 private:
    166  RefPtr<nsWebBrowserPersist> mParent;
    167  nsCOMPtr<nsIURI> mFile;
    168  nsCOMPtr<nsIFile> mDataPath;
    169 
    170  uint32_t mPendingDocuments;
    171  nsresult mStatus;
    172 
    173  virtual ~OnWalk() = default;
    174 };
    175 
    176 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk,
    177                  nsIWebBrowserPersistResourceVisitor)
    178 
    179 class nsWebBrowserPersist::OnRemoteWalk final
    180    : public nsIWebBrowserPersistDocumentReceiver {
    181 public:
    182  OnRemoteWalk(nsIWebBrowserPersistResourceVisitor* aVisitor,
    183               nsIWebBrowserPersistDocument* aDocument)
    184      : mVisitor(aVisitor), mDocument(aDocument) {}
    185 
    186  NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER
    187  NS_DECL_ISUPPORTS
    188 private:
    189  nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor;
    190  nsCOMPtr<nsIWebBrowserPersistDocument> mDocument;
    191 
    192  virtual ~OnRemoteWalk() = default;
    193 };
    194 
    195 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnRemoteWalk,
    196                  nsIWebBrowserPersistDocumentReceiver)
    197 
    198 class nsWebBrowserPersist::OnWrite final
    199    : public nsIWebBrowserPersistWriteCompletion {
    200 public:
    201  OnWrite(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aLocalFile)
    202      : mParent(aParent), mFile(aFile), mLocalFile(aLocalFile) {}
    203 
    204  NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION
    205  NS_DECL_ISUPPORTS
    206 private:
    207  RefPtr<nsWebBrowserPersist> mParent;
    208  nsCOMPtr<nsIURI> mFile;
    209  nsCOMPtr<nsIFile> mLocalFile;
    210 
    211  virtual ~OnWrite() = default;
    212 };
    213 
    214 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite,
    215                  nsIWebBrowserPersistWriteCompletion)
    216 
    217 class nsWebBrowserPersist::FlatURIMap final
    218    : public nsIWebBrowserPersistURIMap {
    219 public:
    220  explicit FlatURIMap(const nsACString& aTargetBase)
    221      : mTargetBase(aTargetBase) {}
    222 
    223  void Add(const nsACString& aMapFrom, const nsACString& aMapTo) {
    224    mMapFrom.AppendElement(aMapFrom);
    225    mMapTo.AppendElement(aMapTo);
    226  }
    227 
    228  NS_DECL_NSIWEBBROWSERPERSISTURIMAP
    229  NS_DECL_ISUPPORTS
    230 
    231 private:
    232  nsTArray<nsCString> mMapFrom;
    233  nsTArray<nsCString> mMapTo;
    234  nsCString mTargetBase;
    235 
    236  virtual ~FlatURIMap() = default;
    237 };
    238 
    239 NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap)
    240 
    241 NS_IMETHODIMP
    242 nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum) {
    243  MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
    244  *aNum = mMapTo.Length();
    245  return NS_OK;
    246 }
    247 
    248 NS_IMETHODIMP
    249 nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase) {
    250  aTargetBase = mTargetBase;
    251  return NS_OK;
    252 }
    253 
    254 NS_IMETHODIMP
    255 nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex,
    256                                               nsACString& aMapFrom,
    257                                               nsACString& aMapTo) {
    258  MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length());
    259  if (aIndex >= mMapTo.Length()) {
    260    return NS_ERROR_INVALID_ARG;
    261  }
    262  aMapFrom = mMapFrom[aIndex];
    263  aMapTo = mMapTo[aIndex];
    264  return NS_OK;
    265 }
    266 
    267 // Maximum file length constant. The max file name length is
    268 // volume / server dependent but it is difficult to obtain
    269 // that information. Instead this constant is a reasonable value that
    270 // modern systems should able to cope with.
    271 const uint32_t kDefaultMaxFilenameLength = 64;
    272 
    273 // Default flags for persistence
    274 const uint32_t kDefaultPersistFlags =
    275    nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
    276    nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES;
    277 
    278 // String bundle where error messages come from
    279 const char* kWebBrowserPersistStringBundle =
    280    "chrome://global/locale/nsWebBrowserPersist.properties";
    281 
    282 nsWebBrowserPersist::nsWebBrowserPersist()
    283    : mCurrentDataPathIsRelative(false),
    284      mCurrentThingsToPersist(0),
    285      mOutputMapMutex("nsWebBrowserPersist::mOutputMapMutex"),
    286      mFirstAndOnlyUse(true),
    287      mSavingDocument(false),
    288      mCancel(false),
    289      mEndCalled(false),
    290      mCompleted(false),
    291      mStartSaving(false),
    292      mReplaceExisting(true),
    293      mSerializingOutput(false),
    294      mIsPrivate(false),
    295      mPersistFlags(kDefaultPersistFlags),
    296      mPersistResult(NS_OK),
    297      mTotalCurrentProgress(0),
    298      mTotalMaxProgress(0),
    299      mWrapColumn(72),
    300      mEncodingFlags(0) {}
    301 
    302 nsWebBrowserPersist::~nsWebBrowserPersist() { Cleanup(); }
    303 
    304 //*****************************************************************************
    305 // nsWebBrowserPersist::nsISupports
    306 //*****************************************************************************
    307 
    308 NS_IMPL_ADDREF(nsWebBrowserPersist)
    309 NS_IMPL_RELEASE(nsWebBrowserPersist)
    310 
    311 NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist)
    312  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist)
    313  NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist)
    314  NS_INTERFACE_MAP_ENTRY(nsICancelable)
    315  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
    316  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
    317  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
    318  NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
    319  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
    320  NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
    321 NS_INTERFACE_MAP_END
    322 
    323 //*****************************************************************************
    324 // nsWebBrowserPersist::nsIInterfaceRequestor
    325 //*****************************************************************************
    326 
    327 NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID& aIID,
    328                                                void** aIFace) {
    329  NS_ENSURE_ARG_POINTER(aIFace);
    330 
    331  *aIFace = nullptr;
    332 
    333  nsresult rv = QueryInterface(aIID, aIFace);
    334  if (NS_SUCCEEDED(rv)) {
    335    return rv;
    336  }
    337 
    338  if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
    339                            aIID.Equals(NS_GET_IID(nsIPrompt)))) {
    340    mProgressListener->QueryInterface(aIID, aIFace);
    341    if (*aIFace) return NS_OK;
    342  }
    343 
    344  nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener);
    345  if (req) {
    346    return req->GetInterface(aIID, aIFace);
    347  }
    348 
    349  return NS_ERROR_NO_INTERFACE;
    350 }
    351 
    352 //*****************************************************************************
    353 // nsWebBrowserPersist::nsIWebBrowserPersist
    354 //*****************************************************************************
    355 
    356 NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t* aPersistFlags) {
    357  NS_ENSURE_ARG_POINTER(aPersistFlags);
    358  *aPersistFlags = mPersistFlags;
    359  return NS_OK;
    360 }
    361 NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags) {
    362  mPersistFlags = aPersistFlags;
    363  mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES);
    364  mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT);
    365  return NS_OK;
    366 }
    367 
    368 NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t* aCurrentState) {
    369  NS_ENSURE_ARG_POINTER(aCurrentState);
    370  if (mCompleted) {
    371    *aCurrentState = PERSIST_STATE_FINISHED;
    372  } else if (mFirstAndOnlyUse) {
    373    *aCurrentState = PERSIST_STATE_SAVING;
    374  } else {
    375    *aCurrentState = PERSIST_STATE_READY;
    376  }
    377  return NS_OK;
    378 }
    379 
    380 NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult* aResult) {
    381  NS_ENSURE_ARG_POINTER(aResult);
    382  *aResult = mPersistResult;
    383  return NS_OK;
    384 }
    385 
    386 NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener(
    387    nsIWebProgressListener** aProgressListener) {
    388  NS_ENSURE_ARG_POINTER(aProgressListener);
    389  *aProgressListener = mProgressListener;
    390  NS_IF_ADDREF(*aProgressListener);
    391  return NS_OK;
    392 }
    393 
    394 NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener(
    395    nsIWebProgressListener* aProgressListener) {
    396  mProgressListener = aProgressListener;
    397  mProgressListener2 = do_QueryInterface(aProgressListener);
    398  mEventSink = do_GetInterface(aProgressListener);
    399  return NS_OK;
    400 }
    401 
    402 NS_IMETHODIMP nsWebBrowserPersist::SaveURI(
    403    nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aCacheKey,
    404    nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
    405    nsIInputStream* aPostData, const char* aExtraHeaders, nsISupports* aFile,
    406    nsContentPolicyType aContentPolicy, bool aIsPrivate) {
    407  NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
    408  mFirstAndOnlyUse = false;  // Stop people from reusing this object!
    409 
    410  nsCOMPtr<nsIURI> fileAsURI;
    411  nsresult rv;
    412  rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
    413  NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
    414 
    415  // SaveURIInternal doesn't like broken uris.
    416  mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
    417  rv = SaveURIInternal(aURI, aPrincipal, aContentPolicy, aCacheKey,
    418                       aReferrerInfo, aCookieJarSettings, aPostData,
    419                       aExtraHeaders, fileAsURI, false, aIsPrivate);
    420  return NS_FAILED(rv) ? rv : NS_OK;
    421 }
    422 
    423 NS_IMETHODIMP nsWebBrowserPersist::SaveChannel(nsIChannel* aChannel,
    424                                               nsISupports* aFile) {
    425  NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
    426  mFirstAndOnlyUse = false;  // Stop people from reusing this object!
    427 
    428  nsCOMPtr<nsIURI> fileAsURI;
    429  nsresult rv;
    430  rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
    431  NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
    432 
    433  rv = aChannel->GetURI(getter_AddRefs(mURI));
    434  NS_ENSURE_SUCCESS(rv, rv);
    435 
    436  // SaveChannelInternal doesn't like broken uris.
    437  mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
    438  rv = SaveChannelInternal(aChannel, fileAsURI, false);
    439  return NS_FAILED(rv) ? rv : NS_OK;
    440 }
    441 
    442 NS_IMETHODIMP nsWebBrowserPersist::SaveDocument(nsISupports* aDocument,
    443                                                nsISupports* aFile,
    444                                                nsISupports* aDataPath,
    445                                                const char* aOutputContentType,
    446                                                uint32_t aEncodingFlags,
    447                                                uint32_t aWrapColumn) {
    448  NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
    449  mFirstAndOnlyUse = false;  // Stop people from reusing this object!
    450 
    451  // We need a STATE_IS_NETWORK start/stop pair to bracket the
    452  // notification callbacks.  For a whole document we generate those
    453  // here and in EndDownload(), but for the single-request methods
    454  // that's done in On{Start,Stop}Request instead.
    455  mSavingDocument = true;
    456 
    457  NS_ENSURE_ARG_POINTER(aDocument);
    458  NS_ENSURE_ARG_POINTER(aFile);
    459 
    460  nsCOMPtr<nsIURI> fileAsURI;
    461  nsCOMPtr<nsIURI> datapathAsURI;
    462  nsresult rv;
    463 
    464  rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
    465  NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
    466  if (aDataPath) {
    467    rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI));
    468    NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
    469  }
    470 
    471  mWrapColumn = aWrapColumn;
    472  mEncodingFlags = aEncodingFlags;
    473 
    474  if (aOutputContentType) {
    475    mContentType.AssignASCII(aOutputContentType);
    476  }
    477 
    478  // State start notification
    479  if (mProgressListener) {
    480    mProgressListener->OnStateChange(
    481        nullptr, nullptr,
    482        nsIWebProgressListener::STATE_START |
    483            nsIWebProgressListener::STATE_IS_NETWORK,
    484        NS_OK);
    485  }
    486 
    487  nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument);
    488  if (!doc) {
    489    nsCOMPtr<Document> localDoc = do_QueryInterface(aDocument);
    490    if (localDoc) {
    491      doc = new mozilla::WebBrowserPersistLocalDocument(localDoc);
    492    } else {
    493      rv = NS_ERROR_NO_INTERFACE;
    494    }
    495  }
    496 
    497  bool closed = false;
    498  if (doc && NS_SUCCEEDED(doc->GetIsClosed(&closed)) && !closed) {
    499    rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI);
    500  }
    501 
    502  if (NS_FAILED(rv) || closed) {
    503    SendErrorStatusChange(true, rv, nullptr, mURI);
    504    EndDownload(rv);
    505  }
    506  return rv;
    507 }
    508 
    509 NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) {
    510  // No point cancelling if we're already complete.
    511  if (mEndCalled) {
    512    return NS_OK;
    513  }
    514  mCancel = true;
    515  EndDownload(aReason);
    516  return NS_OK;
    517 }
    518 
    519 NS_IMETHODIMP nsWebBrowserPersist::CancelSave() {
    520  return Cancel(NS_BINDING_ABORTED);
    521 }
    522 
    523 nsresult nsWebBrowserPersist::StartUpload(nsIStorageStream* storStream,
    524                                          nsIURI* aDestinationURI,
    525                                          const nsACString& aContentType) {
    526  // setup the upload channel if the destination is not local
    527  nsCOMPtr<nsIInputStream> inputstream;
    528  nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream));
    529  NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE);
    530  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    531  return StartUpload(inputstream, aDestinationURI, aContentType);
    532 }
    533 
    534 nsresult nsWebBrowserPersist::StartUpload(nsIInputStream* aInputStream,
    535                                          nsIURI* aDestinationURI,
    536                                          const nsACString& aContentType) {
    537  nsCOMPtr<nsIChannel> destChannel;
    538  CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel));
    539  nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(destChannel));
    540  NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE);
    541 
    542  // Set the upload stream
    543  // NOTE: ALL data must be available in "inputstream"
    544  nsresult rv = uploadChannel->SetUploadStream(aInputStream, aContentType, -1);
    545  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    546  rv = destChannel->AsyncOpen(this);
    547  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    548 
    549  // add this to the upload list
    550  nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
    551  mUploadList.InsertOrUpdate(keyPtr, MakeUnique<UploadData>(aDestinationURI));
    552 
    553  return NS_OK;
    554 }
    555 
    556 void nsWebBrowserPersist::SerializeNextFile() {
    557  nsresult rv = NS_OK;
    558  MOZ_ASSERT(mWalkStack.Length() == 0);
    559 
    560  // First, handle gathered URIs.
    561  // This is potentially O(n^2), when taking into account the
    562  // number of times this method is called.  If it becomes a
    563  // bottleneck, the count of not-yet-persisted URIs could be
    564  // maintained separately, and we can skip iterating mURIMap if there are none.
    565 
    566  // Persist each file in the uri map. The document(s)
    567  // will be saved after the last one of these is saved.
    568  for (const auto& entry : mURIMap) {
    569    URIData* data = entry.GetWeak();
    570 
    571    if (!data->mNeedsPersisting || data->mSaved) {
    572      continue;
    573    }
    574 
    575    // Create a URI from the key.
    576    nsCOMPtr<nsIURI> uri;
    577    rv = NS_NewURI(getter_AddRefs(uri), entry.GetKey(), data->mCharset.get());
    578    if (NS_WARN_IF(NS_FAILED(rv))) {
    579      break;
    580    }
    581 
    582    // Make a URI to save the data to.
    583    nsCOMPtr<nsIURI> fileAsURI = data->mDataPath;
    584    rv = AppendPathToURI(fileAsURI, data->mFilename, fileAsURI);
    585    if (NS_WARN_IF(NS_FAILED(rv))) {
    586      break;
    587    }
    588 
    589    rv = SaveURIInternal(uri, data->mTriggeringPrincipal,
    590                         data->mContentPolicyType, 0, nullptr,
    591                         data->mCookieJarSettings, nullptr, nullptr, fileAsURI,
    592                         true, mIsPrivate);
    593    // If SaveURIInternal fails, then it will have called EndDownload,
    594    // which means that |data| is no longer valid memory. We MUST bail.
    595    if (NS_WARN_IF(NS_FAILED(rv))) {
    596      break;
    597    }
    598 
    599    if (rv == NS_OK) {
    600      // URIData.mFile will be updated to point to the correct
    601      // URI object when it is fixed up with the right file extension
    602      // in OnStartRequest
    603      data->mFile = fileAsURI;
    604      data->mSaved = true;
    605    } else {
    606      data->mNeedsFixup = false;
    607    }
    608 
    609    if (mSerializingOutput) {
    610      break;
    611    }
    612  }
    613 
    614  // If there are downloads happening, wait until they're done; the
    615  // OnStopRequest handler will call this method again.
    616  if (mOutputMap.Count() > 0) {
    617    return;
    618  }
    619 
    620  // If serializing, also wait until last upload is done.
    621  if (mSerializingOutput && mUploadList.Count() > 0) {
    622    return;
    623  }
    624 
    625  // If there are also no more documents, then we're done.
    626  if (mDocList.Length() == 0) {
    627    // ...or not quite done, if there are still uploads.
    628    if (mUploadList.Count() > 0) {
    629      return;
    630    }
    631    // Finish and clean things up.  Defer this because the caller
    632    // may have been expecting to use the listeners that that
    633    // method will clear.
    634    NS_DispatchToCurrentThread(
    635        NewRunnableMethod("nsWebBrowserPersist::FinishDownload", this,
    636                          &nsWebBrowserPersist::FinishDownload));
    637    return;
    638  }
    639 
    640  // There are no URIs to save, so just save the next document.
    641  mStartSaving = true;
    642  mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0));
    643  mDocList.RemoveElementAt(0);  // O(n^2) but probably doesn't matter.
    644  MOZ_ASSERT(docData);
    645  if (!docData) {
    646    EndDownload(NS_ERROR_FAILURE);
    647    return;
    648  }
    649 
    650  mCurrentBaseURI = docData->mBaseURI;
    651  mCurrentCharset = docData->mCharset;
    652  mTargetBaseURI = docData->mFile;
    653 
    654  // Save the document, fixing it up with the new URIs as we do
    655 
    656  nsAutoCString targetBaseSpec;
    657  if (mTargetBaseURI) {
    658    rv = mTargetBaseURI->GetSpec(targetBaseSpec);
    659    if (NS_FAILED(rv)) {
    660      SendErrorStatusChange(true, rv, nullptr, nullptr);
    661      EndDownload(rv);
    662      return;
    663    }
    664  }
    665 
    666  // mFlatURIMap must be rebuilt each time through SerializeNextFile, as
    667  // mTargetBaseURI is used to create the relative URLs and will be different
    668  // with each serialized document.
    669  RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec);
    670  for (const auto& uriEntry : mURIMap) {
    671    nsAutoCString mapTo;
    672    nsresult rv = uriEntry.GetWeak()->GetLocalURI(mTargetBaseURI, mapTo);
    673    if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) {
    674      flatMap->Add(uriEntry.GetKey(), mapTo);
    675    }
    676  }
    677  mFlatURIMap = std::move(flatMap);
    678 
    679  nsCOMPtr<nsIFile> localFile;
    680  GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile));
    681  if (localFile) {
    682    // if we're not replacing an existing file but the file
    683    // exists, something is wrong
    684    bool fileExists = false;
    685    rv = localFile->Exists(&fileExists);
    686    if (NS_SUCCEEDED(rv) && !mReplaceExisting && fileExists) {
    687      rv = NS_ERROR_FILE_ALREADY_EXISTS;
    688    }
    689    if (NS_FAILED(rv)) {
    690      SendErrorStatusChange(false, rv, nullptr, docData->mFile);
    691      EndDownload(rv);
    692      return;
    693    }
    694  }
    695  nsCOMPtr<nsIOutputStream> outputStream;
    696  rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream));
    697  if (NS_SUCCEEDED(rv) && !outputStream) {
    698    rv = NS_ERROR_FAILURE;
    699  }
    700  if (NS_FAILED(rv)) {
    701    SendErrorStatusChange(false, rv, nullptr, docData->mFile);
    702    EndDownload(rv);
    703    return;
    704  }
    705 
    706  RefPtr<OnWrite> finish = new OnWrite(this, docData->mFile, localFile);
    707  rv = docData->mDocument->WriteContent(outputStream, mFlatURIMap,
    708                                        NS_ConvertUTF16toUTF8(mContentType),
    709                                        mEncodingFlags, mWrapColumn, finish);
    710  if (NS_FAILED(rv)) {
    711    SendErrorStatusChange(false, rv, nullptr, docData->mFile);
    712    EndDownload(rv);
    713  }
    714 }
    715 
    716 NS_IMETHODIMP
    717 nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc,
    718                                       nsIOutputStream* aStream,
    719                                       const nsACString& aContentType,
    720                                       nsresult aStatus) {
    721  nsresult rv = aStatus;
    722 
    723  if (NS_FAILED(rv)) {
    724    mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
    725    mParent->EndDownload(rv);
    726    return NS_OK;
    727  }
    728  if (!mLocalFile) {
    729    nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream));
    730    if (storStream) {
    731      aStream->Close();
    732      rv = mParent->StartUpload(storStream, mFile, aContentType);
    733      if (NS_FAILED(rv)) {
    734        mParent->SendErrorStatusChange(false, rv, nullptr, mFile);
    735        mParent->EndDownload(rv);
    736      }
    737      // Either we failed and we're done, or we're uploading and
    738      // the OnStopRequest callback is responsible for the next
    739      // SerializeNextFile().
    740      return NS_OK;
    741    }
    742  }
    743  NS_DispatchToCurrentThread(
    744      NewRunnableMethod("nsWebBrowserPersist::SerializeNextFile", mParent,
    745                        &nsWebBrowserPersist::SerializeNextFile));
    746  return NS_OK;
    747 }
    748 
    749 //*****************************************************************************
    750 // nsWebBrowserPersist::nsIRequestObserver
    751 //*****************************************************************************
    752 
    753 NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest(nsIRequest* request) {
    754  if (mProgressListener) {
    755    uint32_t stateFlags = nsIWebProgressListener::STATE_START |
    756                          nsIWebProgressListener::STATE_IS_REQUEST;
    757    if (!mSavingDocument) {
    758      stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
    759    }
    760    mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK);
    761  }
    762 
    763  nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
    764  NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
    765 
    766  nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
    767  OutputData* data = mOutputMap.Get(keyPtr);
    768 
    769  // NOTE: This code uses the channel as a hash key so it will not
    770  //       recognize redirected channels because the key is not the same.
    771  //       When that happens we remove and add the data entry to use the
    772  //       new channel as the hash key.
    773  if (!data) {
    774    UploadData* upData = mUploadList.Get(keyPtr);
    775    if (!upData) {
    776      // Redirect? Try and fixup the output table
    777      nsresult rv = FixRedirectedChannelEntry(channel);
    778      NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    779 
    780      // Should be able to find the data after fixup unless redirects
    781      // are disabled.
    782      data = mOutputMap.Get(keyPtr);
    783      if (!data) {
    784        return NS_ERROR_FAILURE;
    785      }
    786    }
    787  }
    788 
    789  if (data && data->mFile) {
    790    nsCOMPtr<nsIThreadRetargetableRequest> r = do_QueryInterface(request);
    791    // Determine if we're uploading. Only use OMT onDataAvailable if not.
    792    nsCOMPtr<nsIFile> localFile;
    793    GetLocalFileFromURI(data->mFile, getter_AddRefs(localFile));
    794    if (r && localFile) {
    795      if (!mBackgroundQueue) {
    796        NS_CreateBackgroundTaskQueue("WebBrowserPersist",
    797                                     getter_AddRefs(mBackgroundQueue));
    798      }
    799      if (mBackgroundQueue) {
    800        r->RetargetDeliveryTo(mBackgroundQueue);
    801      }
    802    }
    803 
    804    // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags,
    805    // try to determine whether this channel needs to apply Content-Encoding
    806    // conversions.
    807    NS_ASSERTION(
    808        !((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) &&
    809          (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)),
    810        "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set");
    811    if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION)
    812      SetApplyConversionIfNeeded(channel);
    813 
    814    if (data->mCalcFileExt &&
    815        !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)) {
    816      nsCOMPtr<nsIURI> uriWithExt;
    817      // this is the first point at which the server can tell us the mimetype
    818      nsresult rv = CalculateAndAppendFileExt(
    819          data->mFile, channel, data->mOriginalLocation, uriWithExt);
    820      if (NS_SUCCEEDED(rv)) {
    821        data->mFile = uriWithExt;
    822      }
    823 
    824      // now make filename conformant and unique
    825      nsCOMPtr<nsIURI> uniqueFilenameURI;
    826      rv = CalculateUniqueFilename(data->mFile, uniqueFilenameURI);
    827      if (NS_SUCCEEDED(rv)) {
    828        data->mFile = uniqueFilenameURI;
    829      }
    830 
    831      // The URIData entry is pointing to the old unfixed URI, so we need
    832      // to update it.
    833      nsCOMPtr<nsIURI> chanURI;
    834      rv = channel->GetOriginalURI(getter_AddRefs(chanURI));
    835      if (NS_SUCCEEDED(rv)) {
    836        nsAutoCString spec;
    837        chanURI->GetSpec(spec);
    838        URIData* uridata;
    839        if (mURIMap.Get(spec, &uridata)) {
    840          uridata->mFile = data->mFile;
    841        }
    842      }
    843    }
    844 
    845    // compare uris and bail before we add to output map if they are equal
    846    bool isEqual = false;
    847    if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual)) &&
    848        isEqual) {
    849      {
    850        MutexAutoLock lock(mOutputMapMutex);
    851        // remove from output map
    852        mOutputMap.Remove(keyPtr);
    853      }
    854 
    855      // cancel; we don't need to know any more
    856      // stop request will get called
    857      request->Cancel(NS_BINDING_ABORTED);
    858    }
    859  }
    860 
    861  return NS_OK;
    862 }
    863 
    864 NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest(nsIRequest* request,
    865                                                 nsresult status) {
    866  nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
    867  OutputData* data = mOutputMap.Get(keyPtr);
    868  if (data) {
    869    if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) {
    870      SendErrorStatusChange(true, status, request, data->mFile);
    871    }
    872 
    873    // If there is a stream ref and we weren't canceled,
    874    // close it away from the main thread.
    875    // We don't do this when there's an error/cancelation,
    876    // because our consumer may try to delete the file, which will error
    877    // if we're still holding on to it, so we have to close it pronto.
    878    {
    879      MutexAutoLock lock(data->mStreamMutex);
    880      if (data->mStream && NS_SUCCEEDED(status) && !mCancel) {
    881        if (!mBackgroundQueue) {
    882          nsresult rv = NS_CreateBackgroundTaskQueue(
    883              "WebBrowserPersist", getter_AddRefs(mBackgroundQueue));
    884          if (NS_FAILED(rv)) {
    885            return rv;
    886          }
    887        }
    888        // Now steal the stream ref and close it away from the main thread,
    889        // keeping the promise around so we don't finish before all files
    890        // are flushed and closed.
    891        mFileClosePromises.AppendElement(InvokeAsync(
    892            mBackgroundQueue, __func__, [stream = std::move(data->mStream)]() {
    893              nsresult rv = stream->Close();
    894              // We don't care if closing failed; we don't care in the
    895              // destructor either...
    896              return ClosePromise::CreateAndResolve(rv, __func__);
    897            }));
    898      }
    899    }
    900    MutexAutoLock lock(mOutputMapMutex);
    901    mOutputMap.Remove(keyPtr);
    902  } else {
    903    // if we didn't find the data in mOutputMap, try mUploadList
    904    UploadData* upData = mUploadList.Get(keyPtr);
    905    if (upData) {
    906      mUploadList.Remove(keyPtr);
    907    }
    908  }
    909 
    910  // Do more work.
    911  SerializeNextFile();
    912 
    913  if (mProgressListener) {
    914    uint32_t stateFlags = nsIWebProgressListener::STATE_STOP |
    915                          nsIWebProgressListener::STATE_IS_REQUEST;
    916    if (!mSavingDocument) {
    917      stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
    918    }
    919    mProgressListener->OnStateChange(nullptr, request, stateFlags, status);
    920  }
    921 
    922  return NS_OK;
    923 }
    924 
    925 //*****************************************************************************
    926 // nsWebBrowserPersist::nsIStreamListener
    927 //*****************************************************************************
    928 
    929 // Note: this is supposed to (but not guaranteed to) fire on a background
    930 // thread when used to save to local disk (channels not using local files will
    931 // use the main thread).
    932 // (Read) Access to mOutputMap is guarded via mOutputMapMutex.
    933 // Access to individual OutputData::mStream is guarded via its mStreamMutex.
    934 // mCancel is atomic, as is mPersistFlags (accessed via MakeOutputStream).
    935 // If you end up touching this method and needing other member access, bear
    936 // this in mind.
    937 NS_IMETHODIMP
    938 nsWebBrowserPersist::OnDataAvailable(nsIRequest* request,
    939                                     nsIInputStream* aIStream, uint64_t aOffset,
    940                                     uint32_t aLength) {
    941  // MOZ_ASSERT(!NS_IsMainThread()); // no guarantees, but it's likely.
    942 
    943  bool cancel = mCancel;
    944  if (!cancel) {
    945    nsresult rv = NS_OK;
    946    uint32_t bytesRemaining = aLength;
    947 
    948    nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
    949    NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
    950 
    951    MutexAutoLock lock(mOutputMapMutex);
    952    nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
    953    OutputData* data = mOutputMap.Get(keyPtr);
    954    if (!data) {
    955      // might be uploadData; consume necko's buffer and bail...
    956      uint32_t n;
    957      return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n);
    958    }
    959 
    960    bool readError = true;
    961 
    962    MutexAutoLock streamLock(data->mStreamMutex);
    963    // Make the output stream
    964    if (!data->mStream) {
    965      rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream));
    966      if (NS_FAILED(rv)) {
    967        readError = false;
    968        cancel = true;
    969      }
    970    }
    971 
    972    // Read data from the input and write to the output
    973    char buffer[8192];
    974    uint32_t bytesRead;
    975    while (!cancel && bytesRemaining) {
    976      readError = true;
    977      rv = aIStream->Read(buffer,
    978                          std::min(uint32_t(sizeof(buffer)), bytesRemaining),
    979                          &bytesRead);
    980      if (NS_SUCCEEDED(rv)) {
    981        readError = false;
    982        // Write out the data until something goes wrong, or, it is
    983        // all written.  We loop because for some errors (e.g., disk
    984        // full), we get NS_OK with some bytes written, then an error.
    985        // So, we want to write again in that case to get the actual
    986        // error code.
    987        const char* bufPtr = buffer;  // Where to write from.
    988        while (NS_SUCCEEDED(rv) && bytesRead) {
    989          uint32_t bytesWritten = 0;
    990          rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten);
    991          if (NS_SUCCEEDED(rv)) {
    992            bytesRead -= bytesWritten;
    993            bufPtr += bytesWritten;
    994            bytesRemaining -= bytesWritten;
    995            // Force an error if (for some reason) we get NS_OK but
    996            // no bytes written.
    997            if (!bytesWritten) {
    998              rv = NS_ERROR_FAILURE;
    999              cancel = true;
   1000            }
   1001          } else {
   1002            // Disaster - can't write out the bytes - disk full / permission?
   1003            cancel = true;
   1004          }
   1005        }
   1006      } else {
   1007        // Disaster - can't read the bytes - broken link / file error?
   1008        cancel = true;
   1009      }
   1010    }
   1011 
   1012    int64_t channelContentLength = -1;
   1013    if (!cancel &&
   1014        NS_SUCCEEDED(channel->GetContentLength(&channelContentLength))) {
   1015      // if we get -1 at this point, we didn't get content-length header
   1016      // assume that we got all of the data and push what we have;
   1017      // that's the best we can do now
   1018      if ((-1 == channelContentLength) ||
   1019          ((channelContentLength - (aOffset + aLength)) == 0)) {
   1020        NS_WARNING_ASSERTION(
   1021            channelContentLength != -1,
   1022            "nsWebBrowserPersist::OnDataAvailable() no content length "
   1023            "header, pushing what we have");
   1024        // we're done with this pass; see if we need to do upload
   1025        nsAutoCString contentType;
   1026        channel->GetContentType(contentType);
   1027        // if we don't have the right type of output stream then it's a local
   1028        // file
   1029        nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream));
   1030        if (storStream) {
   1031          data->mStream->Close();
   1032          data->mStream =
   1033              nullptr;  // null out stream so we don't close it later
   1034          MOZ_ASSERT(NS_IsMainThread(),
   1035                     "Uploads should be on the main thread.");
   1036          rv = StartUpload(storStream, data->mFile, contentType);
   1037          if (NS_FAILED(rv)) {
   1038            readError = false;
   1039            cancel = true;
   1040          }
   1041        }
   1042      }
   1043    }
   1044 
   1045    // Notify listener if an error occurred.
   1046    if (cancel) {
   1047      RefPtr<nsIRequest> req = readError ? request : nullptr;
   1048      nsCOMPtr<nsIURI> file = data->mFile;
   1049      RefPtr<Runnable> errorOnMainThread = NS_NewRunnableFunction(
   1050          "nsWebBrowserPersist::SendErrorStatusChange",
   1051          [self = RefPtr{this}, req, file, readError, rv]() {
   1052            self->SendErrorStatusChange(readError, rv, req, file);
   1053          });
   1054      NS_DispatchToMainThread(errorOnMainThread);
   1055 
   1056      // And end the download on the main thread.
   1057      nsCOMPtr<nsIRunnable> endOnMainThread = NewRunnableMethod<nsresult>(
   1058          "nsWebBrowserPersist::EndDownload", this,
   1059          &nsWebBrowserPersist::EndDownload, NS_BINDING_ABORTED);
   1060      NS_DispatchToMainThread(endOnMainThread);
   1061    }
   1062  }
   1063 
   1064  return cancel ? NS_BINDING_ABORTED : NS_OK;
   1065 }
   1066 
   1067 //*****************************************************************************
   1068 // nsWebBrowserPersist::nsIThreadRetargetableStreamListener
   1069 //*****************************************************************************
   1070 
   1071 NS_IMETHODIMP nsWebBrowserPersist::CheckListenerChain() { return NS_OK; }
   1072 
   1073 NS_IMETHODIMP
   1074 nsWebBrowserPersist::OnDataFinished(nsresult) { return NS_OK; }
   1075 
   1076 //*****************************************************************************
   1077 // nsWebBrowserPersist::nsIProgressEventSink
   1078 //*****************************************************************************
   1079 
   1080 NS_IMETHODIMP nsWebBrowserPersist::OnProgress(nsIRequest* request,
   1081                                              int64_t aProgress,
   1082                                              int64_t aProgressMax) {
   1083  if (!mProgressListener) {
   1084    return NS_OK;
   1085  }
   1086 
   1087  // Store the progress of this request
   1088  nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
   1089  OutputData* data = mOutputMap.Get(keyPtr);
   1090  if (data) {
   1091    data->mSelfProgress = aProgress;
   1092    data->mSelfProgressMax = aProgressMax;
   1093  } else {
   1094    UploadData* upData = mUploadList.Get(keyPtr);
   1095    if (upData) {
   1096      upData->mSelfProgress = aProgress;
   1097      upData->mSelfProgressMax = aProgressMax;
   1098    }
   1099  }
   1100 
   1101  // Notify listener of total progress
   1102  CalcTotalProgress();
   1103  if (mProgressListener2) {
   1104    mProgressListener2->OnProgressChange64(nullptr, request, aProgress,
   1105                                           aProgressMax, mTotalCurrentProgress,
   1106                                           mTotalMaxProgress);
   1107  } else {
   1108    // have to truncate 64-bit to 32bit
   1109    mProgressListener->OnProgressChange(
   1110        nullptr, request, uint64_t(aProgress), uint64_t(aProgressMax),
   1111        mTotalCurrentProgress, mTotalMaxProgress);
   1112  }
   1113 
   1114  // If our progress listener implements nsIProgressEventSink,
   1115  // forward the notification
   1116  if (mEventSink) {
   1117    mEventSink->OnProgress(request, aProgress, aProgressMax);
   1118  }
   1119 
   1120  return NS_OK;
   1121 }
   1122 
   1123 NS_IMETHODIMP nsWebBrowserPersist::OnStatus(nsIRequest* request,
   1124                                            nsresult status,
   1125                                            const char16_t* statusArg) {
   1126  if (mProgressListener) {
   1127    // We need to filter out non-error error codes.
   1128    // Is the only NS_SUCCEEDED value NS_OK?
   1129    switch (status) {
   1130      case NS_NET_STATUS_RESOLVING_HOST:
   1131      case NS_NET_STATUS_RESOLVED_HOST:
   1132      case NS_NET_STATUS_CONNECTING_TO:
   1133      case NS_NET_STATUS_CONNECTED_TO:
   1134      case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
   1135      case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
   1136      case NS_NET_STATUS_SENDING_TO:
   1137      case NS_NET_STATUS_RECEIVING_FROM:
   1138      case NS_NET_STATUS_WAITING_FOR:
   1139      case NS_NET_STATUS_READING:
   1140      case NS_NET_STATUS_WRITING:
   1141        break;
   1142 
   1143      default:
   1144        // Pass other notifications (for legitimate errors) along.
   1145        mProgressListener->OnStatusChange(nullptr, request, status, statusArg);
   1146        break;
   1147    }
   1148  }
   1149 
   1150  // If our progress listener implements nsIProgressEventSink,
   1151  // forward the notification
   1152  if (mEventSink) {
   1153    mEventSink->OnStatus(request, status, statusArg);
   1154  }
   1155 
   1156  return NS_OK;
   1157 }
   1158 
   1159 //*****************************************************************************
   1160 // nsWebBrowserPersist private methods
   1161 //*****************************************************************************
   1162 
   1163 // Convert error info into proper message text and send OnStatusChange
   1164 // notification to the web progress listener.
   1165 nsresult nsWebBrowserPersist::SendErrorStatusChange(bool aIsReadError,
   1166                                                    nsresult aResult,
   1167                                                    nsIRequest* aRequest,
   1168                                                    nsIURI* aURI) {
   1169  NS_ENSURE_ARG_POINTER(aURI);
   1170 
   1171  if (!mProgressListener) {
   1172    // Do nothing
   1173    return NS_OK;
   1174  }
   1175 
   1176  // Get the file path or spec from the supplied URI
   1177  nsCOMPtr<nsIFile> file;
   1178  GetLocalFileFromURI(aURI, getter_AddRefs(file));
   1179  AutoTArray<nsString, 1> strings;
   1180  nsresult rv;
   1181  if (file) {
   1182    file->GetPath(*strings.AppendElement());
   1183  } else {
   1184    nsAutoCString fileurl;
   1185    rv = aURI->GetSpec(fileurl);
   1186    NS_ENSURE_SUCCESS(rv, rv);
   1187    CopyUTF8toUTF16(fileurl, *strings.AppendElement());
   1188  }
   1189 
   1190  const char* msgId;
   1191  switch (aResult) {
   1192    case NS_ERROR_FILE_NAME_TOO_LONG:
   1193      // File name too long.
   1194      msgId = "fileNameTooLongError";
   1195      break;
   1196    case NS_ERROR_FILE_ALREADY_EXISTS:
   1197      // File exists with same name as directory.
   1198      msgId = "fileAlreadyExistsError";
   1199      break;
   1200    case NS_ERROR_FILE_NO_DEVICE_SPACE:
   1201      // Out of space on target volume.
   1202      msgId = "diskFull";
   1203      break;
   1204 
   1205    case NS_ERROR_FILE_READ_ONLY:
   1206      // Attempt to write to read/only file.
   1207      msgId = "readOnly";
   1208      break;
   1209 
   1210    case NS_ERROR_FILE_ACCESS_DENIED:
   1211      // Attempt to write without sufficient permissions.
   1212      msgId = "accessError";
   1213      break;
   1214 
   1215    default:
   1216      // Generic read/write error message.
   1217      if (aIsReadError)
   1218        msgId = "readError";
   1219      else
   1220        msgId = "writeError";
   1221      break;
   1222  }
   1223  // Get properties file bundle and extract status string.
   1224  nsCOMPtr<nsIStringBundleService> s =
   1225      do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
   1226  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE);
   1227 
   1228  nsCOMPtr<nsIStringBundle> bundle;
   1229  rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle));
   1230  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE);
   1231 
   1232  nsAutoString msgText;
   1233  rv = bundle->FormatStringFromName(msgId, strings, msgText);
   1234  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   1235 
   1236  mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText.get());
   1237 
   1238  return NS_OK;
   1239 }
   1240 
   1241 nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports* aObject,
   1242                                                    nsIURI** aURI) const {
   1243  NS_ENSURE_ARG_POINTER(aObject);
   1244  NS_ENSURE_ARG_POINTER(aURI);
   1245 
   1246  nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject);
   1247  if (objAsFile) {
   1248    return NS_NewFileURI(aURI, objAsFile);
   1249  }
   1250  nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject);
   1251  if (objAsURI) {
   1252    *aURI = objAsURI;
   1253    NS_ADDREF(*aURI);
   1254    return NS_OK;
   1255  }
   1256 
   1257  return NS_ERROR_FAILURE;
   1258 }
   1259 
   1260 /* static */
   1261 nsresult nsWebBrowserPersist::GetLocalFileFromURI(nsIURI* aURI,
   1262                                                  nsIFile** aLocalFile) {
   1263  nsresult rv;
   1264 
   1265  nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
   1266  if (NS_FAILED(rv)) return rv;
   1267 
   1268  nsCOMPtr<nsIFile> file;
   1269  rv = fileURL->GetFile(getter_AddRefs(file));
   1270  if (NS_FAILED(rv)) {
   1271    return rv;
   1272  }
   1273 
   1274  file.forget(aLocalFile);
   1275  return NS_OK;
   1276 }
   1277 
   1278 /* static */
   1279 nsresult nsWebBrowserPersist::AppendPathToURI(nsIURI* aURI,
   1280                                              const nsAString& aPath,
   1281                                              nsCOMPtr<nsIURI>& aOutURI) {
   1282  NS_ENSURE_ARG_POINTER(aURI);
   1283 
   1284  nsAutoCString newPath;
   1285  nsresult rv = aURI->GetPathQueryRef(newPath);
   1286  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   1287 
   1288  // Append a forward slash if necessary
   1289  int32_t len = newPath.Length();
   1290  if (len > 0 && newPath.CharAt(len - 1) != '/') {
   1291    newPath.Append('/');
   1292  }
   1293 
   1294  // Store the path back on the URI
   1295  AppendUTF16toUTF8(aPath, newPath);
   1296 
   1297  return NS_MutateURI(aURI).SetPathQueryRef(newPath).Finalize(aOutURI);
   1298 }
   1299 
   1300 nsresult nsWebBrowserPersist::SaveURIInternal(
   1301    nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
   1302    nsContentPolicyType aContentPolicyType, uint32_t aCacheKey,
   1303    nsIReferrerInfo* aReferrerInfo, nsICookieJarSettings* aCookieJarSettings,
   1304    nsIInputStream* aPostData, const char* aExtraHeaders, nsIURI* aFile,
   1305    bool aCalcFileExt, bool aIsPrivate) {
   1306  NS_ENSURE_ARG_POINTER(aURI);
   1307  NS_ENSURE_ARG_POINTER(aFile);
   1308  NS_ENSURE_ARG_POINTER(aTriggeringPrincipal);
   1309 
   1310  nsresult rv = NS_OK;
   1311 
   1312  mURI = aURI;
   1313 
   1314  nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
   1315  if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE) {
   1316    loadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
   1317  } else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE) {
   1318    loadFlags |= nsIRequest::LOAD_FROM_CACHE;
   1319  }
   1320 
   1321  // If there is no cookieJarSetting given, we need to create a new
   1322  // cookieJarSettings for this download in order to send cookies based on the
   1323  // current state of the prefs/permissions.
   1324  nsCOMPtr<nsICookieJarSettings> cookieJarSettings = aCookieJarSettings;
   1325  if (!cookieJarSettings) {
   1326    // Although the variable is called 'triggering principal', it is used as the
   1327    // loading principal in the download channel, so we treat it as a loading
   1328    // principal also.
   1329    bool shouldResistFingerprinting =
   1330        nsContentUtils::ShouldResistFingerprinting_dangerous(
   1331            aTriggeringPrincipal,
   1332            "We are creating a new CookieJar Settings, so none exists "
   1333            "currently. Although the variable is called 'triggering principal',"
   1334            "it is used as the loading principal in the download channel, so we"
   1335            "treat it as a loading principal also.",
   1336            RFPTarget::IsAlwaysEnabledForPrecompute);
   1337    cookieJarSettings =
   1338        aIsPrivate
   1339            ? net::CookieJarSettings::Create(net::CookieJarSettings::ePrivate,
   1340                                             shouldResistFingerprinting)
   1341            : net::CookieJarSettings::Create(net::CookieJarSettings::eRegular,
   1342                                             shouldResistFingerprinting);
   1343  }
   1344 
   1345  // Open a channel to the URI
   1346  nsCOMPtr<nsIChannel> inputChannel;
   1347  rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI, aTriggeringPrincipal,
   1348                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
   1349                     aContentPolicyType, cookieJarSettings,
   1350                     nullptr,  // aPerformanceStorage
   1351                     nullptr,  // aLoadGroup
   1352                     static_cast<nsIInterfaceRequestor*>(this), loadFlags);
   1353 
   1354  nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel =
   1355      do_QueryInterface(inputChannel);
   1356  if (pbChannel) {
   1357    pbChannel->SetPrivate(aIsPrivate);
   1358  }
   1359 
   1360  if (NS_FAILED(rv) || inputChannel == nullptr) {
   1361    EndDownload(NS_ERROR_FAILURE);
   1362    return NS_ERROR_FAILURE;
   1363  }
   1364 
   1365  // Disable content conversion
   1366  if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION) {
   1367    nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(inputChannel));
   1368    if (encodedChannel) {
   1369      encodedChannel->SetApplyConversion(false);
   1370    }
   1371  }
   1372 
   1373  nsCOMPtr<nsILoadInfo> loadInfo = inputChannel->LoadInfo();
   1374  loadInfo->SetIsUserTriggeredSave(true);
   1375  if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_DISABLE_HTTPS_ONLY) {
   1376    loadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT);
   1377  }
   1378 
   1379  // Set the referrer, post data and headers if any
   1380  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
   1381  if (httpChannel) {
   1382    if (aReferrerInfo) {
   1383      DebugOnly<nsresult> success = httpChannel->SetReferrerInfo(aReferrerInfo);
   1384      MOZ_ASSERT(NS_SUCCEEDED(success));
   1385    }
   1386 
   1387    // Post data
   1388    if (aPostData) {
   1389      nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData));
   1390      if (stream) {
   1391        // Rewind the postdata stream
   1392        stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
   1393        nsCOMPtr<nsIUploadChannel> uploadChannel(
   1394            do_QueryInterface(httpChannel));
   1395        NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
   1396        // Attach the postdata to the http channel
   1397        uploadChannel->SetUploadStream(aPostData, ""_ns, -1);
   1398      }
   1399    }
   1400 
   1401    // Cache key
   1402    nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
   1403    if (cacheChannel && aCacheKey != 0) {
   1404      cacheChannel->SetCacheKey(aCacheKey);
   1405    }
   1406 
   1407    // Headers
   1408    if (aExtraHeaders) {
   1409      rv = mozilla::net::AddExtraHeaders(httpChannel,
   1410                                         nsDependentCString(aExtraHeaders));
   1411      if (NS_FAILED(rv)) {
   1412        EndDownload(NS_ERROR_FAILURE);
   1413        return NS_ERROR_FAILURE;
   1414      }
   1415    }
   1416  }
   1417  return SaveChannelInternal(inputChannel, aFile, aCalcFileExt);
   1418 }
   1419 
   1420 nsresult nsWebBrowserPersist::SaveChannelInternal(nsIChannel* aChannel,
   1421                                                  nsIURI* aFile,
   1422                                                  bool aCalcFileExt) {
   1423  NS_ENSURE_ARG_POINTER(aChannel);
   1424  NS_ENSURE_ARG_POINTER(aFile);
   1425 
   1426  // The default behaviour of SaveChannelInternal is to download the source
   1427  // into a storage stream and upload that to the target. MakeOutputStream
   1428  // special-cases a file target and creates a file output stream directly.
   1429  // We want to special-case a file source and create a file input stream,
   1430  // but we don't need to do this in the case of a file target.
   1431  nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(aChannel));
   1432  nsCOMPtr<nsIFileURL> fu(do_QueryInterface(aFile));
   1433 
   1434  if (fc && !fu) {
   1435    nsCOMPtr<nsIInputStream> fileInputStream, bufferedInputStream;
   1436    nsresult rv = aChannel->Open(getter_AddRefs(fileInputStream));
   1437    NS_ENSURE_SUCCESS(rv, rv);
   1438    rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream),
   1439                                   fileInputStream.forget(),
   1440                                   BUFFERED_OUTPUT_SIZE);
   1441    NS_ENSURE_SUCCESS(rv, rv);
   1442    nsAutoCString contentType;
   1443    aChannel->GetContentType(contentType);
   1444    return StartUpload(bufferedInputStream, aFile, contentType);
   1445  }
   1446 
   1447  // Mark save channel as throttleable.
   1448  nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(aChannel));
   1449  if (cos) {
   1450    cos->AddClassFlags(nsIClassOfService::Throttleable);
   1451  }
   1452 
   1453  // Read from the input channel
   1454  nsresult rv = aChannel->AsyncOpen(this);
   1455  if (rv == NS_ERROR_NO_CONTENT) {
   1456    // Assume this is a protocol such as mailto: which does not feed out
   1457    // data and just ignore it.
   1458    return NS_SUCCESS_DONT_FIXUP;
   1459  }
   1460 
   1461  if (NS_FAILED(rv)) {
   1462    // Opening failed, but do we care?
   1463    if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS) {
   1464      SendErrorStatusChange(true, rv, aChannel, aFile);
   1465      EndDownload(NS_ERROR_FAILURE);
   1466      return NS_ERROR_FAILURE;
   1467    }
   1468    return NS_SUCCESS_DONT_FIXUP;
   1469  }
   1470 
   1471  MutexAutoLock lock(mOutputMapMutex);
   1472  // Add the output transport to the output map with the channel as the key
   1473  nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel);
   1474  mOutputMap.InsertOrUpdate(keyPtr,
   1475                            MakeUnique<OutputData>(aFile, mURI, aCalcFileExt));
   1476 
   1477  return NS_OK;
   1478 }
   1479 
   1480 nsresult nsWebBrowserPersist::GetExtensionForContentType(
   1481    const char16_t* aContentType, char16_t** aExt) {
   1482  NS_ENSURE_ARG_POINTER(aContentType);
   1483  NS_ENSURE_ARG_POINTER(aExt);
   1484 
   1485  *aExt = nullptr;
   1486 
   1487  nsresult rv;
   1488  if (!mMIMEService) {
   1489    mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
   1490    NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
   1491  }
   1492 
   1493  nsAutoCString contentType;
   1494  LossyCopyUTF16toASCII(MakeStringSpan(aContentType), contentType);
   1495  nsAutoCString ext;
   1496  rv = mMIMEService->GetPrimaryExtension(contentType, ""_ns, ext);
   1497  if (NS_SUCCEEDED(rv)) {
   1498    *aExt = UTF8ToNewUnicode(ext);
   1499    NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY);
   1500    return NS_OK;
   1501  }
   1502 
   1503  return NS_ERROR_FAILURE;
   1504 }
   1505 
   1506 nsresult nsWebBrowserPersist::SaveDocumentDeferred(
   1507    mozilla::UniquePtr<WalkData>&& aData) {
   1508  nsresult rv =
   1509      SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath);
   1510  if (NS_FAILED(rv)) {
   1511    SendErrorStatusChange(true, rv, nullptr, mURI);
   1512    EndDownload(rv);
   1513  }
   1514  return rv;
   1515 }
   1516 
   1517 nsresult nsWebBrowserPersist::SaveDocumentInternal(
   1518    nsIWebBrowserPersistDocument* aDocument, nsIURI* aFile, nsIURI* aDataPath) {
   1519  mURI = nullptr;
   1520  NS_ENSURE_ARG_POINTER(aDocument);
   1521  NS_ENSURE_ARG_POINTER(aFile);
   1522 
   1523  nsresult rv = aDocument->SetPersistFlags(mPersistFlags);
   1524  NS_ENSURE_SUCCESS(rv, rv);
   1525 
   1526  rv = aDocument->GetIsPrivate(&mIsPrivate);
   1527  NS_ENSURE_SUCCESS(rv, rv);
   1528 
   1529  // See if we can get the local file representation of this URI
   1530  nsCOMPtr<nsIFile> localFile;
   1531  rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
   1532 
   1533  nsCOMPtr<nsIFile> localDataPath;
   1534  if (NS_SUCCEEDED(rv) && aDataPath) {
   1535    // See if we can get the local file representation of this URI
   1536    rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath));
   1537    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   1538  }
   1539 
   1540  // Persist the main document
   1541  rv = aDocument->GetCharacterSet(mCurrentCharset);
   1542  NS_ENSURE_SUCCESS(rv, rv);
   1543  nsAutoCString uriSpec;
   1544  rv = aDocument->GetDocumentURI(uriSpec);
   1545  NS_ENSURE_SUCCESS(rv, rv);
   1546  rv = NS_NewURI(getter_AddRefs(mURI), uriSpec, mCurrentCharset.get());
   1547  NS_ENSURE_SUCCESS(rv, rv);
   1548  rv = aDocument->GetBaseURI(uriSpec);
   1549  NS_ENSURE_SUCCESS(rv, rv);
   1550  rv = NS_NewURI(getter_AddRefs(mCurrentBaseURI), uriSpec,
   1551                 mCurrentCharset.get());
   1552  NS_ENSURE_SUCCESS(rv, rv);
   1553 
   1554  // Does the caller want to fixup the referenced URIs and save those too?
   1555  if (aDataPath) {
   1556    // Basic steps are these.
   1557    //
   1558    // 1. Iterate through the document (and subdocuments) building a list
   1559    //    of unique URIs.
   1560    // 2. For each URI create an OutputData entry and open a channel to save
   1561    //    it. As each URI is saved, discover the mime type and fix up the
   1562    //    local filename with the correct extension.
   1563    // 3. Store the document in a list and wait for URI persistence to finish
   1564    // 4. After URI persistence completes save the list of documents,
   1565    //    fixing it up as it goes out to file.
   1566 
   1567    mCurrentDataPathIsRelative = false;
   1568    mCurrentDataPath = aDataPath;
   1569    mCurrentRelativePathToData = "";
   1570    mCurrentThingsToPersist = 0;
   1571    mTargetBaseURI = aFile;
   1572 
   1573    // Determine if the specified data path is relative to the
   1574    // specified file, (e.g. c:\docs\htmldata is relative to
   1575    // c:\docs\myfile.htm, but not to d:\foo\data.
   1576 
   1577    // Starting with the data dir work back through its parents
   1578    // checking if one of them matches the base directory.
   1579 
   1580    if (localDataPath && localFile) {
   1581      nsCOMPtr<nsIFile> baseDir;
   1582      localFile->GetParent(getter_AddRefs(baseDir));
   1583 
   1584      nsAutoCString relativePathToData;
   1585      nsCOMPtr<nsIFile> dataDirParent;
   1586      dataDirParent = localDataPath;
   1587      while (dataDirParent) {
   1588        bool sameDir = false;
   1589        dataDirParent->Equals(baseDir, &sameDir);
   1590        if (sameDir) {
   1591          mCurrentRelativePathToData = relativePathToData;
   1592          mCurrentDataPathIsRelative = true;
   1593          break;
   1594        }
   1595 
   1596        nsAutoString dirName;
   1597        dataDirParent->GetLeafName(dirName);
   1598 
   1599        nsAutoCString newRelativePathToData;
   1600        newRelativePathToData =
   1601            NS_ConvertUTF16toUTF8(dirName) + "/"_ns + relativePathToData;
   1602        relativePathToData = newRelativePathToData;
   1603 
   1604        nsCOMPtr<nsIFile> newDataDirParent;
   1605        rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent));
   1606        dataDirParent = newDataDirParent;
   1607      }
   1608    } else {
   1609      // generate a relative path if possible
   1610      nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile));
   1611      if (pathToBaseURL) {
   1612        nsAutoCString relativePath;  // nsACString
   1613        if (NS_SUCCEEDED(
   1614                pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) {
   1615          mCurrentDataPathIsRelative = true;
   1616          mCurrentRelativePathToData = relativePath;
   1617        }
   1618      }
   1619    }
   1620 
   1621    // Store the document in a list so when URI persistence is done and the
   1622    // filenames of saved URIs are known, the documents can be fixed up and
   1623    // saved
   1624 
   1625    auto* docData = new DocData;
   1626    docData->mBaseURI = mCurrentBaseURI;
   1627    docData->mCharset = mCurrentCharset;
   1628    docData->mDocument = aDocument;
   1629    docData->mFile = aFile;
   1630    mDocList.AppendElement(docData);
   1631 
   1632    // Walk the DOM gathering a list of externally referenced URIs in the uri
   1633    // map
   1634    nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit =
   1635        new OnWalk(this, aFile, localDataPath);
   1636    return aDocument->ReadResources(visit);
   1637  } else {
   1638    auto* docData = new DocData;
   1639    docData->mBaseURI = mCurrentBaseURI;
   1640    docData->mCharset = mCurrentCharset;
   1641    docData->mDocument = aDocument;
   1642    docData->mFile = aFile;
   1643    mDocList.AppendElement(docData);
   1644 
   1645    // Not walking DOMs, so go directly to serialization.
   1646    SerializeNextFile();
   1647    return NS_OK;
   1648  }
   1649 }
   1650 
   1651 NS_IMETHODIMP
   1652 nsWebBrowserPersist::OnWalk::VisitResource(
   1653    nsIWebBrowserPersistDocument* aDoc, const nsACString& aURI,
   1654    nsContentPolicyType aContentPolicyType) {
   1655  return mParent->StoreURI(aURI, aDoc, aContentPolicyType);
   1656 }
   1657 
   1658 NS_IMETHODIMP
   1659 nsWebBrowserPersist::OnWalk::VisitDocument(
   1660    nsIWebBrowserPersistDocument* aDoc, nsIWebBrowserPersistDocument* aSubDoc) {
   1661  URIData* data = nullptr;
   1662  nsAutoCString uriSpec;
   1663  nsresult rv = aSubDoc->GetDocumentURI(uriSpec);
   1664  NS_ENSURE_SUCCESS(rv, rv);
   1665  rv = mParent->StoreURI(uriSpec, aDoc, nsIContentPolicy::TYPE_SUBDOCUMENT,
   1666                         false, &data);
   1667  NS_ENSURE_SUCCESS(rv, rv);
   1668  if (!data) {
   1669    // If the URI scheme isn't persistable, then don't persist.
   1670    return NS_OK;
   1671  }
   1672  data->mIsSubFrame = true;
   1673  return mParent->SaveSubframeContent(aSubDoc, aDoc, uriSpec, data);
   1674 }
   1675 
   1676 NS_IMETHODIMP
   1677 nsWebBrowserPersist::OnWalk::VisitBrowsingContext(
   1678    nsIWebBrowserPersistDocument* aDoc, BrowsingContext* aContext) {
   1679  RefPtr<dom::CanonicalBrowsingContext> context = aContext->Canonical();
   1680 
   1681  if (NS_WARN_IF(!context->GetCurrentWindowGlobal())) {
   1682    EndVisit(nullptr, NS_ERROR_FAILURE);
   1683    return NS_ERROR_FAILURE;
   1684  }
   1685 
   1686  RefPtr<WebBrowserPersistDocumentParent> actor(
   1687      new WebBrowserPersistDocumentParent());
   1688 
   1689  nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> receiver =
   1690      new OnRemoteWalk(this, aDoc);
   1691  actor->SetOnReady(receiver);
   1692 
   1693  RefPtr<dom::BrowserParent> browserParent =
   1694      context->GetCurrentWindowGlobal()->GetBrowserParent();
   1695 
   1696  bool ok =
   1697      context->GetContentParent()->SendPWebBrowserPersistDocumentConstructor(
   1698          actor, browserParent, context);
   1699 
   1700  if (NS_WARN_IF(!ok)) {
   1701    // (The actor will be destroyed on constructor failure.)
   1702    EndVisit(nullptr, NS_ERROR_FAILURE);
   1703    return NS_ERROR_FAILURE;
   1704  }
   1705 
   1706  ++mPendingDocuments;
   1707 
   1708  return NS_OK;
   1709 }
   1710 
   1711 NS_IMETHODIMP
   1712 nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc,
   1713                                      nsresult aStatus) {
   1714  if (NS_FAILED(mStatus)) {
   1715    return mStatus;
   1716  }
   1717 
   1718  if (NS_FAILED(aStatus)) {
   1719    mStatus = aStatus;
   1720    mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile);
   1721    mParent->EndDownload(aStatus);
   1722    return aStatus;
   1723  }
   1724 
   1725  if (--mPendingDocuments) {
   1726    // We're not done yet, wait for more.
   1727    return NS_OK;
   1728  }
   1729 
   1730  mParent->FinishSaveDocumentInternal(mFile, mDataPath);
   1731  return NS_OK;
   1732 }
   1733 
   1734 NS_IMETHODIMP
   1735 nsWebBrowserPersist::OnRemoteWalk::OnDocumentReady(
   1736    nsIWebBrowserPersistDocument* aSubDocument) {
   1737  mVisitor->VisitDocument(mDocument, aSubDocument);
   1738  mVisitor->EndVisit(mDocument, NS_OK);
   1739  return NS_OK;
   1740 }
   1741 
   1742 NS_IMETHODIMP
   1743 nsWebBrowserPersist::OnRemoteWalk::OnError(nsresult aFailure) {
   1744  mVisitor->EndVisit(nullptr, aFailure);
   1745  return NS_OK;
   1746 }
   1747 
   1748 void nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile,
   1749                                                     nsIFile* aDataPath) {
   1750  // If there are things to persist, create a directory to hold them
   1751  if (mCurrentThingsToPersist > 0) {
   1752    if (aDataPath) {
   1753      bool exists = false;
   1754      bool haveDir = false;
   1755 
   1756      aDataPath->Exists(&exists);
   1757      if (exists) {
   1758        aDataPath->IsDirectory(&haveDir);
   1759      }
   1760      if (!haveDir) {
   1761        nsresult rv = aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
   1762        if (NS_SUCCEEDED(rv)) {
   1763          haveDir = true;
   1764        } else {
   1765          SendErrorStatusChange(false, rv, nullptr, aFile);
   1766        }
   1767      }
   1768      if (!haveDir) {
   1769        EndDownload(NS_ERROR_FAILURE);
   1770        return;
   1771      }
   1772      if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) {
   1773        // Add to list of things to delete later if all goes wrong
   1774        auto* cleanupData = new CleanupData;
   1775        cleanupData->mFile = aDataPath;
   1776        cleanupData->mIsDirectory = true;
   1777        mCleanupList.AppendElement(cleanupData);
   1778      }
   1779    }
   1780  }
   1781 
   1782  if (mWalkStack.Length() > 0) {
   1783    mozilla::UniquePtr<WalkData> toWalk = mWalkStack.PopLastElement();
   1784    // Bounce this off the event loop to avoid stack overflow.
   1785    using WalkStorage = StoreCopyPassByRRef<decltype(toWalk)>;
   1786    auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred;
   1787    nsCOMPtr<nsIRunnable> saveLater = NewRunnableMethod<WalkStorage>(
   1788        "nsWebBrowserPersist::FinishSaveDocumentInternal", this, saveMethod,
   1789        std::move(toWalk));
   1790    NS_DispatchToCurrentThread(saveLater);
   1791  } else {
   1792    // Done walking DOMs; on to the serialization phase.
   1793    SerializeNextFile();
   1794  }
   1795 }
   1796 
   1797 void nsWebBrowserPersist::Cleanup() {
   1798  mURIMap.Clear();
   1799  nsClassHashtable<nsISupportsHashKey, OutputData> outputMapCopy;
   1800  {
   1801    MutexAutoLock lock(mOutputMapMutex);
   1802    mOutputMap.SwapElements(outputMapCopy);
   1803  }
   1804  for (const auto& key : outputMapCopy.Keys()) {
   1805    nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
   1806    if (channel) {
   1807      channel->Cancel(NS_BINDING_ABORTED);
   1808    }
   1809  }
   1810  outputMapCopy.Clear();
   1811 
   1812  for (const auto& key : mUploadList.Keys()) {
   1813    nsCOMPtr<nsIChannel> channel = do_QueryInterface(key);
   1814    if (channel) {
   1815      channel->Cancel(NS_BINDING_ABORTED);
   1816    }
   1817  }
   1818  mUploadList.Clear();
   1819 
   1820  uint32_t i;
   1821  for (i = 0; i < mDocList.Length(); i++) {
   1822    DocData* docData = mDocList.ElementAt(i);
   1823    delete docData;
   1824  }
   1825  mDocList.Clear();
   1826 
   1827  for (i = 0; i < mCleanupList.Length(); i++) {
   1828    CleanupData* cleanupData = mCleanupList.ElementAt(i);
   1829    delete cleanupData;
   1830  }
   1831  mCleanupList.Clear();
   1832 
   1833  mFilenameList.Clear();
   1834 }
   1835 
   1836 void nsWebBrowserPersist::CleanupLocalFiles() {
   1837  // Two passes, the first pass cleans up files, the second pass tests
   1838  // for and then deletes empty directories. Directories that are not
   1839  // empty after the first pass must contain files from something else
   1840  // and are not deleted.
   1841  int pass;
   1842  for (pass = 0; pass < 2; pass++) {
   1843    uint32_t i;
   1844    for (i = 0; i < mCleanupList.Length(); i++) {
   1845      CleanupData* cleanupData = mCleanupList.ElementAt(i);
   1846      nsCOMPtr<nsIFile> file = cleanupData->mFile;
   1847 
   1848      // Test if the dir / file exists (something in an earlier loop
   1849      // may have already removed it)
   1850      bool exists = false;
   1851      file->Exists(&exists);
   1852      if (!exists) continue;
   1853 
   1854      // Test if the file has changed in between creation and deletion
   1855      // in some way that means it should be ignored
   1856      bool isDirectory = false;
   1857      file->IsDirectory(&isDirectory);
   1858      if (isDirectory != cleanupData->mIsDirectory)
   1859        continue;  // A file has become a dir or vice versa !
   1860 
   1861      if (pass == 0 && !isDirectory) {
   1862        file->Remove(false);
   1863      } else if (pass == 1 && isDirectory)  // Directory
   1864      {
   1865        // Directories are more complicated. Enumerate through
   1866        // children looking for files. Any files created by the
   1867        // persist object would have been deleted by the first
   1868        // pass so if there are any there at this stage, the dir
   1869        // cannot be deleted because it has someone else's files
   1870        // in it. Empty child dirs are deleted but they must be
   1871        // recursed through to ensure they are actually empty.
   1872 
   1873        bool isEmptyDirectory = true;
   1874        nsCOMArray<nsIDirectoryEnumerator> dirStack;
   1875        int32_t stackSize = 0;
   1876 
   1877        // Push the top level enum onto the stack
   1878        nsCOMPtr<nsIDirectoryEnumerator> pos;
   1879        if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos))))
   1880          dirStack.AppendObject(pos);
   1881 
   1882        while (isEmptyDirectory && (stackSize = dirStack.Count())) {
   1883          // Pop the last element
   1884          nsCOMPtr<nsIDirectoryEnumerator> curPos;
   1885          curPos = dirStack[stackSize - 1];
   1886          dirStack.RemoveObjectAt(stackSize - 1);
   1887 
   1888          nsCOMPtr<nsIFile> child;
   1889          if (NS_FAILED(curPos->GetNextFile(getter_AddRefs(child))) || !child) {
   1890            continue;
   1891          }
   1892 
   1893          bool childIsSymlink = false;
   1894          child->IsSymlink(&childIsSymlink);
   1895          bool childIsDir = false;
   1896          child->IsDirectory(&childIsDir);
   1897          if (!childIsDir || childIsSymlink) {
   1898            // Some kind of file or symlink which means dir
   1899            // is not empty so just drop out.
   1900            isEmptyDirectory = false;
   1901            break;
   1902          }
   1903          // Push parent enumerator followed by child enumerator
   1904          nsCOMPtr<nsIDirectoryEnumerator> childPos;
   1905          child->GetDirectoryEntries(getter_AddRefs(childPos));
   1906          dirStack.AppendObject(curPos);
   1907          if (childPos) dirStack.AppendObject(childPos);
   1908        }
   1909        dirStack.Clear();
   1910 
   1911        // If after all that walking the dir is deemed empty, delete it
   1912        if (isEmptyDirectory) {
   1913          file->Remove(true);
   1914        }
   1915      }
   1916    }
   1917  }
   1918 }
   1919 
   1920 nsresult nsWebBrowserPersist::CalculateUniqueFilename(
   1921    nsIURI* aURI, nsCOMPtr<nsIURI>& aOutURI) {
   1922  nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
   1923  NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
   1924 
   1925  bool nameHasChanged = false;
   1926  nsresult rv;
   1927 
   1928  // Get the old filename
   1929  nsAutoCString filename;
   1930  rv = url->GetFileName(filename);
   1931  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   1932  nsAutoCString directory;
   1933  rv = url->GetDirectory(directory);
   1934  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   1935 
   1936  // Split the filename into a base and an extension.
   1937  // e.g. "foo.html" becomes "foo" & ".html"
   1938  //
   1939  // The nsIURL methods GetFileBaseName & GetFileExtension don't
   1940  // preserve the dot whereas this code does to save some effort
   1941  // later when everything is put back together.
   1942  int32_t lastDot = filename.RFind(".");
   1943  nsAutoCString base;
   1944  nsAutoCString ext;
   1945  if (lastDot >= 0) {
   1946    filename.Mid(base, 0, lastDot);
   1947    filename.Mid(ext, lastDot, filename.Length() - lastDot);  // includes dot
   1948  } else {
   1949    // filename contains no dot
   1950    base = filename;
   1951  }
   1952 
   1953  // Test if the filename is longer than allowed by the OS
   1954  int32_t needToChop = filename.Length() - kDefaultMaxFilenameLength;
   1955  if (needToChop > 0) {
   1956    // Truncate the base first and then the ext if necessary
   1957    if (base.Length() > (uint32_t)needToChop) {
   1958      base.Truncate(base.Length() - needToChop);
   1959    } else {
   1960      needToChop -= base.Length() - 1;
   1961      base.Truncate(1);
   1962      if (ext.Length() > (uint32_t)needToChop) {
   1963        ext.Truncate(ext.Length() - needToChop);
   1964      } else {
   1965        ext.Truncate(0);
   1966      }
   1967      // If kDefaultMaxFilenameLength were 1 we'd be in trouble here,
   1968      // but that won't happen because it will be set to a sensible
   1969      // value.
   1970    }
   1971 
   1972    filename.Assign(base);
   1973    filename.Append(ext);
   1974    nameHasChanged = true;
   1975  }
   1976 
   1977  // Ensure the filename is unique
   1978  // Create a filename if it's empty, or if the filename / datapath is
   1979  // already taken by another URI and create an alternate name.
   1980 
   1981  if (base.IsEmpty() || !mFilenameList.IsEmpty()) {
   1982    nsAutoCString tmpPath;
   1983    nsAutoCString tmpBase;
   1984    uint32_t duplicateCounter = 1;
   1985    while (true) {
   1986      // Make a file name,
   1987      // Foo become foo_001, foo_002, etc.
   1988      // Empty files become _001, _002 etc.
   1989 
   1990      if (base.IsEmpty() || duplicateCounter > 1) {
   1991        SmprintfPointer tmp = mozilla::Smprintf("_%03d", duplicateCounter);
   1992        NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY);
   1993        if (filename.Length() < kDefaultMaxFilenameLength - 4) {
   1994          tmpBase = base;
   1995        } else {
   1996          base.Mid(tmpBase, 0, base.Length() - 4);
   1997        }
   1998        tmpBase.Append(tmp.get());
   1999      } else {
   2000        tmpBase = base;
   2001      }
   2002 
   2003      tmpPath.Assign(directory);
   2004      tmpPath.Append(tmpBase);
   2005      tmpPath.Append(ext);
   2006 
   2007      // Test if the name is a duplicate
   2008      if (!mFilenameList.Contains(tmpPath)) {
   2009        if (!base.Equals(tmpBase)) {
   2010          filename.Assign(tmpBase);
   2011          filename.Append(ext);
   2012          nameHasChanged = true;
   2013        }
   2014        break;
   2015      }
   2016      duplicateCounter++;
   2017    }
   2018  }
   2019 
   2020  // Add name to list of those already used
   2021  nsAutoCString newFilepath(directory);
   2022  newFilepath.Append(filename);
   2023  mFilenameList.AppendElement(newFilepath);
   2024 
   2025  // Update the uri accordingly if the filename actually changed
   2026  if (nameHasChanged) {
   2027    // Final sanity test
   2028    if (filename.Length() > kDefaultMaxFilenameLength) {
   2029      NS_WARNING(
   2030          "Filename wasn't truncated less than the max file length - how can "
   2031          "that be?");
   2032      return NS_ERROR_FAILURE;
   2033    }
   2034 
   2035    nsCOMPtr<nsIFile> localFile;
   2036    GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
   2037 
   2038    if (localFile) {
   2039      nsAutoString filenameAsUnichar;
   2040      CopyASCIItoUTF16(filename, filenameAsUnichar);
   2041      localFile->SetLeafName(filenameAsUnichar);
   2042 
   2043      // Resync the URI with the file after the extension has been appended
   2044      return NS_MutateURI(aURI)
   2045          .Apply(&nsIFileURLMutator::SetFile, localFile)
   2046          .Finalize(aOutURI);
   2047    }
   2048    return NS_MutateURI(url)
   2049        .Apply(&nsIURLMutator::SetFileName, filename, nullptr)
   2050        .Finalize(aOutURI);
   2051  }
   2052 
   2053  // TODO (:valentin) This method should always clone aURI
   2054  aOutURI = aURI;
   2055  return NS_OK;
   2056 }
   2057 
   2058 nsresult nsWebBrowserPersist::MakeFilenameFromURI(nsIURI* aURI,
   2059                                                  nsString& aFilename) {
   2060  // Try to get filename from the URI.
   2061  nsAutoString fileName;
   2062 
   2063  // Get a suggested file name from the URL but strip it of characters
   2064  // likely to cause the name to be illegal.
   2065 
   2066  nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
   2067  if (url) {
   2068    nsAutoCString nameFromURL;
   2069    url->GetFileName(nameFromURL);
   2070    if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) {
   2071      CopyASCIItoUTF16(NS_UnescapeURL(nameFromURL), fileName);
   2072      aFilename = fileName;
   2073      return NS_OK;
   2074    }
   2075    if (!nameFromURL.IsEmpty()) {
   2076      // Unescape the file name (GetFileName escapes it)
   2077      NS_UnescapeURL(nameFromURL);
   2078      uint32_t nameLength = 0;
   2079      const char* p = nameFromURL.get();
   2080      for (; *p && *p != ';' && *p != '?' && *p != '#' && *p != '.'; p++) {
   2081        if (IsAsciiAlpha(*p) || IsAsciiDigit(*p) || *p == '.' || *p == '-' ||
   2082            *p == '_' || (*p == ' ')) {
   2083          fileName.Append(char16_t(*p));
   2084          if (++nameLength == kDefaultMaxFilenameLength) {
   2085            // Note:
   2086            // There is no point going any further since it will be
   2087            // truncated in CalculateUniqueFilename anyway.
   2088            // More importantly, certain implementations of
   2089            // nsIFile (e.g. the Mac impl) might truncate
   2090            // names in undesirable ways, such as truncating from
   2091            // the middle, inserting ellipsis and so on.
   2092            break;
   2093          }
   2094        }
   2095      }
   2096    }
   2097  }
   2098 
   2099  // Empty filenames can confuse the local file object later
   2100  // when it attempts to set the leaf name in CalculateUniqueFilename
   2101  // for duplicates and ends up replacing the parent dir. To avoid
   2102  // the problem, all filenames are made at least one character long.
   2103  if (fileName.IsEmpty()) {
   2104    fileName.Append(char16_t('a'));  // 'a' is for arbitrary
   2105  }
   2106 
   2107  aFilename = fileName;
   2108  return NS_OK;
   2109 }
   2110 
   2111 nsresult nsWebBrowserPersist::CalculateAndAppendFileExt(
   2112    nsIURI* aURI, nsIChannel* aChannel, nsIURI* aOriginalURIWithExtension,
   2113    nsCOMPtr<nsIURI>& aOutURI) {
   2114  nsresult rv = NS_OK;
   2115 
   2116  if (!mMIMEService) {
   2117    mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
   2118    NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
   2119  }
   2120 
   2121  nsAutoCString contentType;
   2122 
   2123  // Get the content type from the channel
   2124  aChannel->GetContentType(contentType);
   2125 
   2126  // Get the content type from the MIME service
   2127  if (contentType.IsEmpty()) {
   2128    nsCOMPtr<nsIURI> uri;
   2129    aChannel->GetOriginalURI(getter_AddRefs(uri));
   2130    mMIMEService->GetTypeFromURI(uri, contentType);
   2131  }
   2132 
   2133  // Validate the filename
   2134  if (!contentType.IsEmpty()) {
   2135    nsAutoString newFileName;
   2136    if (NS_SUCCEEDED(mMIMEService->GetValidFileName(
   2137            aChannel, contentType, aOriginalURIWithExtension,
   2138            nsIMIMEService::VALIDATE_DEFAULT, newFileName))) {
   2139      nsCOMPtr<nsIFile> localFile;
   2140      GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
   2141      if (localFile) {
   2142        localFile->SetLeafName(newFileName);
   2143 
   2144        // Resync the URI with the file after the extension has been appended
   2145        return NS_MutateURI(aURI)
   2146            .Apply(&nsIFileURLMutator::SetFile, localFile)
   2147            .Finalize(aOutURI);
   2148      }
   2149      return NS_MutateURI(aURI)
   2150          .Apply(&nsIURLMutator::SetFileName,
   2151                 NS_ConvertUTF16toUTF8(newFileName), nullptr)
   2152          .Finalize(aOutURI);
   2153    }
   2154  }
   2155 
   2156  // TODO (:valentin) This method should always clone aURI
   2157  aOutURI = aURI;
   2158  return NS_OK;
   2159 }
   2160 
   2161 // Note: the MakeOutputStream helpers can be called from a background thread.
   2162 nsresult nsWebBrowserPersist::MakeOutputStream(
   2163    nsIURI* aURI, nsIOutputStream** aOutputStream) {
   2164  nsresult rv;
   2165 
   2166  nsCOMPtr<nsIFile> localFile;
   2167  GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
   2168  if (localFile)
   2169    rv = MakeOutputStreamFromFile(localFile, aOutputStream);
   2170  else
   2171    rv = MakeOutputStreamFromURI(aURI, aOutputStream);
   2172 
   2173  return rv;
   2174 }
   2175 
   2176 nsresult nsWebBrowserPersist::MakeOutputStreamFromFile(
   2177    nsIFile* aFile, nsIOutputStream** aOutputStream) {
   2178  nsresult rv = NS_OK;
   2179 
   2180  nsCOMPtr<nsIFileOutputStream> fileOutputStream =
   2181      do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
   2182  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   2183 
   2184  // XXX brade:  get the right flags here!
   2185  int32_t ioFlags = -1;
   2186  if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE)
   2187    ioFlags = PR_APPEND | PR_CREATE_FILE | PR_WRONLY;
   2188  rv = fileOutputStream->Init(aFile, ioFlags, -1, 0);
   2189  NS_ENSURE_SUCCESS(rv, rv);
   2190 
   2191  rv = NS_NewBufferedOutputStream(aOutputStream, fileOutputStream.forget(),
   2192                                  BUFFERED_OUTPUT_SIZE);
   2193  NS_ENSURE_SUCCESS(rv, rv);
   2194 
   2195  if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) {
   2196    // Add to cleanup list in event of failure
   2197    auto* cleanupData = new CleanupData;
   2198    cleanupData->mFile = aFile;
   2199    cleanupData->mIsDirectory = false;
   2200    if (NS_IsMainThread()) {
   2201      mCleanupList.AppendElement(cleanupData);
   2202    } else {
   2203      // If we're on a background thread, add the cleanup back on the main
   2204      // thread.
   2205      RefPtr<Runnable> addCleanup = NS_NewRunnableFunction(
   2206          "nsWebBrowserPersist::AddCleanupToList",
   2207          [self = RefPtr{this}, cleanup = std::move(cleanupData)]() {
   2208            self->mCleanupList.AppendElement(cleanup);
   2209          });
   2210      NS_DispatchToMainThread(addCleanup);
   2211    }
   2212  }
   2213 
   2214  return NS_OK;
   2215 }
   2216 
   2217 nsresult nsWebBrowserPersist::MakeOutputStreamFromURI(
   2218    nsIURI* aURI, nsIOutputStream** aOutputStream) {
   2219  uint32_t segsize = 8192;
   2220  uint32_t maxsize = uint32_t(-1);
   2221  nsCOMPtr<nsIStorageStream> storStream;
   2222  nsresult rv =
   2223      NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream));
   2224  NS_ENSURE_SUCCESS(rv, rv);
   2225 
   2226  NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream),
   2227                    NS_ERROR_FAILURE);
   2228  return NS_OK;
   2229 }
   2230 
   2231 void nsWebBrowserPersist::FinishDownload() {
   2232  // We call FinishDownload when we run out of things to download for this
   2233  // persist operation, by dispatching this method to the main thread. By now,
   2234  // it's possible that we have been canceled or encountered an error earlier
   2235  // in the download, or something else called EndDownload. In that case, don't
   2236  // re-run EndDownload.
   2237  if (mEndCalled) {
   2238    return;
   2239  }
   2240  EndDownload(NS_OK);
   2241 }
   2242 
   2243 void nsWebBrowserPersist::EndDownload(nsresult aResult) {
   2244  MOZ_ASSERT(NS_IsMainThread(), "Should end download on the main thread.");
   2245 
   2246  // Really this should just never happen, but if it does, at least avoid
   2247  // no-op notifications or pretending we succeeded if we already failed.
   2248  if (mEndCalled && (NS_SUCCEEDED(aResult) || mPersistResult == aResult)) {
   2249    return;
   2250  }
   2251 
   2252  // Store the error code in the result if it is an error
   2253  if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) {
   2254    mPersistResult = aResult;
   2255  }
   2256 
   2257  if (mEndCalled) {
   2258    MOZ_ASSERT(!mEndCalled, "Should only end the download once.");
   2259    return;
   2260  }
   2261  mEndCalled = true;
   2262 
   2263  ClosePromise::All(GetCurrentSerialEventTarget(), mFileClosePromises)
   2264      ->Then(GetCurrentSerialEventTarget(), __func__,
   2265             [self = RefPtr{this}, aResult]() {
   2266               self->EndDownloadInternal(aResult);
   2267             });
   2268 }
   2269 
   2270 void nsWebBrowserPersist::EndDownloadInternal(nsresult aResult) {
   2271  // mCompleted needs to be set before issuing the stop notification.
   2272  // (Bug 1224437)
   2273  mCompleted = true;
   2274  // State stop notification
   2275  if (mProgressListener) {
   2276    mProgressListener->OnStateChange(
   2277        nullptr, nullptr,
   2278        nsIWebProgressListener::STATE_STOP |
   2279            nsIWebProgressListener::STATE_IS_NETWORK,
   2280        mPersistResult);
   2281  }
   2282 
   2283  // Do file cleanup if required
   2284  if (NS_FAILED(aResult) &&
   2285      (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) {
   2286    CleanupLocalFiles();
   2287  }
   2288 
   2289  // Cleanup the channels
   2290  Cleanup();
   2291 
   2292  mProgressListener = nullptr;
   2293  mProgressListener2 = nullptr;
   2294  mEventSink = nullptr;
   2295 }
   2296 
   2297 nsresult nsWebBrowserPersist::FixRedirectedChannelEntry(
   2298    nsIChannel* aNewChannel) {
   2299  NS_ENSURE_ARG_POINTER(aNewChannel);
   2300 
   2301  // Iterate through existing open channels looking for one with a URI
   2302  // matching the one specified.
   2303  nsCOMPtr<nsIURI> originalURI;
   2304  aNewChannel->GetOriginalURI(getter_AddRefs(originalURI));
   2305  nsISupports* matchingKey = nullptr;
   2306  for (nsISupports* key : mOutputMap.Keys()) {
   2307    nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key);
   2308    nsCOMPtr<nsIURI> thisURI;
   2309 
   2310    thisChannel->GetOriginalURI(getter_AddRefs(thisURI));
   2311 
   2312    // Compare this channel's URI to the one passed in.
   2313    bool matchingURI = false;
   2314    thisURI->Equals(originalURI, &matchingURI);
   2315    if (matchingURI) {
   2316      matchingKey = key;
   2317      break;
   2318    }
   2319  }
   2320 
   2321  if (matchingKey) {
   2322    // We only get called from OnStartRequest, so this is always on the
   2323    // main thread. Make sure we don't pull the rug from under anything else.
   2324    MutexAutoLock lock(mOutputMapMutex);
   2325    // If a match was found, remove the data entry with the old channel
   2326    // key and re-add it with the new channel key.
   2327    mozilla::UniquePtr<OutputData> outputData;
   2328    mOutputMap.Remove(matchingKey, &outputData);
   2329    NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE);
   2330 
   2331    // Store data again with new channel unless told to ignore redirects.
   2332    if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) {
   2333      nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel);
   2334      mOutputMap.InsertOrUpdate(keyPtr, std::move(outputData));
   2335    }
   2336  }
   2337 
   2338  return NS_OK;
   2339 }
   2340 
   2341 void nsWebBrowserPersist::CalcTotalProgress() {
   2342  mTotalCurrentProgress = 0;
   2343  mTotalMaxProgress = 0;
   2344 
   2345  if (mOutputMap.Count() > 0) {
   2346    // Total up the progress of each output stream
   2347    for (const auto& data : mOutputMap.Values()) {
   2348      // Only count toward total progress if destination file is local.
   2349      nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile);
   2350      if (fileURL) {
   2351        mTotalCurrentProgress += data->mSelfProgress;
   2352        mTotalMaxProgress += data->mSelfProgressMax;
   2353      }
   2354    }
   2355  }
   2356 
   2357  if (mUploadList.Count() > 0) {
   2358    // Total up the progress of each upload
   2359    for (const auto& data : mUploadList.Values()) {
   2360      if (data) {
   2361        mTotalCurrentProgress += data->mSelfProgress;
   2362        mTotalMaxProgress += data->mSelfProgressMax;
   2363      }
   2364    }
   2365  }
   2366 
   2367  // XXX this code seems pretty bogus and pointless
   2368  if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) {
   2369    // No output streams so we must be complete
   2370    mTotalCurrentProgress = 10000;
   2371    mTotalMaxProgress = 10000;
   2372  }
   2373 }
   2374 
   2375 nsresult nsWebBrowserPersist::StoreURI(const nsACString& aURI,
   2376                                       nsIWebBrowserPersistDocument* aDoc,
   2377                                       nsContentPolicyType aContentPolicyType,
   2378                                       bool aNeedsPersisting, URIData** aData) {
   2379  nsCOMPtr<nsIURI> uri;
   2380  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, mCurrentCharset.get(),
   2381                          mCurrentBaseURI);
   2382  NS_ENSURE_SUCCESS(rv, rv);
   2383 
   2384  return StoreURI(uri, aDoc, aContentPolicyType, aNeedsPersisting, aData);
   2385 }
   2386 
   2387 nsresult nsWebBrowserPersist::StoreURI(nsIURI* aURI,
   2388                                       nsIWebBrowserPersistDocument* aDoc,
   2389                                       nsContentPolicyType aContentPolicyType,
   2390                                       bool aNeedsPersisting, URIData** aData) {
   2391  NS_ENSURE_ARG_POINTER(aURI);
   2392  if (aData) {
   2393    *aData = nullptr;
   2394  }
   2395 
   2396  // Test if this URI should be persisted. By default
   2397  // we should assume the URI  is persistable.
   2398  bool doNotPersistURI;
   2399  nsresult rv = NS_URIChainHasFlags(
   2400      aURI, nsIProtocolHandler::URI_NON_PERSISTABLE, &doNotPersistURI);
   2401  if (NS_FAILED(rv)) {
   2402    doNotPersistURI = false;
   2403  }
   2404 
   2405  if (doNotPersistURI) {
   2406    return NS_OK;
   2407  }
   2408 
   2409  URIData* data = nullptr;
   2410  MakeAndStoreLocalFilenameInURIMap(aURI, aDoc, aContentPolicyType,
   2411                                    aNeedsPersisting, &data);
   2412  if (aData) {
   2413    *aData = data;
   2414  }
   2415 
   2416  return NS_OK;
   2417 }
   2418 
   2419 nsresult nsWebBrowserPersist::URIData::GetLocalURI(nsIURI* targetBaseURI,
   2420                                                   nsCString& aSpecOut) {
   2421  aSpecOut.SetIsVoid(true);
   2422  if (!mNeedsFixup) {
   2423    return NS_OK;
   2424  }
   2425  nsresult rv;
   2426  nsCOMPtr<nsIURI> fileAsURI;
   2427  if (mFile) {
   2428    fileAsURI = mFile;
   2429  } else {
   2430    fileAsURI = mDataPath;
   2431    rv = AppendPathToURI(fileAsURI, mFilename, fileAsURI);
   2432    NS_ENSURE_SUCCESS(rv, rv);
   2433  }
   2434 
   2435  // remove username/password if present
   2436  (void)NS_MutateURI(fileAsURI).SetUserPass(""_ns).Finalize(fileAsURI);
   2437 
   2438  // reset node attribute
   2439  // Use relative or absolute links
   2440  if (mDataPathIsRelative) {
   2441    bool isEqual = false;
   2442    if (NS_SUCCEEDED(mRelativeDocumentURI->Equals(targetBaseURI, &isEqual)) &&
   2443        isEqual) {
   2444      nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI));
   2445      if (!url) {
   2446        return NS_ERROR_FAILURE;
   2447      }
   2448 
   2449      nsAutoCString filename;
   2450      url->GetFileName(filename);
   2451 
   2452      nsAutoCString rawPathURL(mRelativePathToData);
   2453      rawPathURL.Append(filename);
   2454 
   2455      rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible);
   2456      NS_ENSURE_SUCCESS(rv, rv);
   2457    } else {
   2458      nsAutoCString rawPathURL;
   2459 
   2460      nsCOMPtr<nsIFile> dataFile;
   2461      rv = GetLocalFileFromURI(mFile, getter_AddRefs(dataFile));
   2462      NS_ENSURE_SUCCESS(rv, rv);
   2463 
   2464      nsCOMPtr<nsIFile> docFile;
   2465      rv = GetLocalFileFromURI(targetBaseURI, getter_AddRefs(docFile));
   2466      NS_ENSURE_SUCCESS(rv, rv);
   2467 
   2468      nsCOMPtr<nsIFile> parentDir;
   2469      rv = docFile->GetParent(getter_AddRefs(parentDir));
   2470      NS_ENSURE_SUCCESS(rv, rv);
   2471 
   2472      rv = dataFile->GetRelativePath(parentDir, rawPathURL);
   2473      NS_ENSURE_SUCCESS(rv, rv);
   2474 
   2475      rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible);
   2476      NS_ENSURE_SUCCESS(rv, rv);
   2477    }
   2478  } else {
   2479    fileAsURI->GetSpec(aSpecOut);
   2480  }
   2481  if (mIsSubFrame) {
   2482    AppendUTF16toUTF8(mSubFrameExt, aSpecOut);
   2483  }
   2484 
   2485  return NS_OK;
   2486 }
   2487 
   2488 bool nsWebBrowserPersist::DocumentEncoderExists(const char* aContentType) {
   2489  return do_getDocumentTypeSupportedForEncoding(aContentType);
   2490 }
   2491 
   2492 nsresult nsWebBrowserPersist::SaveSubframeContent(
   2493    nsIWebBrowserPersistDocument* aFrameContent,
   2494    nsIWebBrowserPersistDocument* aParentDocument, const nsCString& aURISpec,
   2495    URIData* aData) {
   2496  NS_ENSURE_ARG_POINTER(aData);
   2497 
   2498  // Extract the content type for the frame's contents.
   2499  nsAutoCString contentType;
   2500  nsresult rv = aFrameContent->GetContentType(contentType);
   2501  NS_ENSURE_SUCCESS(rv, rv);
   2502 
   2503  nsString ext;
   2504  GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(),
   2505                             getter_Copies(ext));
   2506 
   2507  // We must always have an extension so we will try to re-assign
   2508  // the original extension if GetExtensionForContentType fails.
   2509  if (ext.IsEmpty()) {
   2510    nsCOMPtr<nsIURI> docURI;
   2511    rv = NS_NewURI(getter_AddRefs(docURI), aURISpec, mCurrentCharset.get());
   2512    NS_ENSURE_SUCCESS(rv, rv);
   2513 
   2514    nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv));
   2515    nsAutoCString extension;
   2516    if (NS_SUCCEEDED(rv)) {
   2517      url->GetFileExtension(extension);
   2518    } else {
   2519      extension.AssignLiteral("htm");
   2520    }
   2521    aData->mSubFrameExt.Assign(char16_t('.'));
   2522    AppendUTF8toUTF16(extension, aData->mSubFrameExt);
   2523  } else {
   2524    aData->mSubFrameExt.Assign(char16_t('.'));
   2525    aData->mSubFrameExt.Append(ext);
   2526  }
   2527 
   2528  nsString filenameWithExt = aData->mFilename;
   2529  filenameWithExt.Append(aData->mSubFrameExt);
   2530 
   2531  // Work out the path for the subframe
   2532  nsCOMPtr<nsIURI> frameURI = mCurrentDataPath;
   2533  rv = AppendPathToURI(frameURI, filenameWithExt, frameURI);
   2534  NS_ENSURE_SUCCESS(rv, rv);
   2535 
   2536  // Work out the path for the subframe data
   2537  nsCOMPtr<nsIURI> frameDataURI = mCurrentDataPath;
   2538  nsAutoString newFrameDataPath(aData->mFilename);
   2539 
   2540  // Append _data
   2541  newFrameDataPath.AppendLiteral("_data");
   2542  rv = AppendPathToURI(frameDataURI, newFrameDataPath, frameDataURI);
   2543  NS_ENSURE_SUCCESS(rv, rv);
   2544 
   2545  // Make frame document & data path conformant and unique
   2546  nsCOMPtr<nsIURI> out;
   2547  rv = CalculateUniqueFilename(frameURI, out);
   2548  NS_ENSURE_SUCCESS(rv, rv);
   2549  frameURI = out;
   2550 
   2551  rv = CalculateUniqueFilename(frameDataURI, out);
   2552  NS_ENSURE_SUCCESS(rv, rv);
   2553  frameDataURI = out;
   2554 
   2555  mCurrentThingsToPersist++;
   2556 
   2557  // We shouldn't use SaveDocumentInternal for the contents
   2558  // of frames that are not documents, e.g. images.
   2559  if (DocumentEncoderExists(contentType.get())) {
   2560    auto toWalk = mozilla::MakeUnique<WalkData>();
   2561    toWalk->mDocument = aFrameContent;
   2562    toWalk->mFile = frameURI;
   2563    toWalk->mDataPath = frameDataURI;
   2564    mWalkStack.AppendElement(std::move(toWalk));
   2565  } else {
   2566    nsContentPolicyType policyType = nsIContentPolicy::TYPE_OTHER;
   2567    if (StringBeginsWith(contentType, "image/"_ns)) {
   2568      policyType = nsIContentPolicy::TYPE_IMAGE;
   2569    } else if (StringBeginsWith(contentType, "audio/"_ns) ||
   2570               StringBeginsWith(contentType, "video/"_ns)) {
   2571      policyType = nsIContentPolicy::TYPE_MEDIA;
   2572    }
   2573    rv = StoreURI(aURISpec, aParentDocument, policyType);
   2574  }
   2575  NS_ENSURE_SUCCESS(rv, rv);
   2576 
   2577  // Store the updated uri to the frame
   2578  aData->mFile = frameURI;
   2579  aData->mSubFrameExt.Truncate();  // we already put this in frameURI
   2580 
   2581  return NS_OK;
   2582 }
   2583 
   2584 nsresult nsWebBrowserPersist::CreateChannelFromURI(nsIURI* aURI,
   2585                                                   nsIChannel** aChannel) {
   2586  nsresult rv = NS_OK;
   2587  *aChannel = nullptr;
   2588 
   2589  rv = NS_NewChannel(aChannel, aURI, nsContentUtils::GetSystemPrincipal(),
   2590                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
   2591                     nsIContentPolicy::TYPE_OTHER);
   2592  NS_ENSURE_SUCCESS(rv, rv);
   2593  NS_ENSURE_ARG_POINTER(*aChannel);
   2594 
   2595  rv = (*aChannel)->SetNotificationCallbacks(
   2596      static_cast<nsIInterfaceRequestor*>(this));
   2597  NS_ENSURE_SUCCESS(rv, rv);
   2598  return NS_OK;
   2599 }
   2600 
   2601 // we store the current location as the key (absolutized version of domnode's
   2602 // attribute's value)
   2603 nsresult nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap(
   2604    nsIURI* aURI, nsIWebBrowserPersistDocument* aDoc,
   2605    nsContentPolicyType aContentPolicyType, bool aNeedsPersisting,
   2606    URIData** aData) {
   2607  NS_ENSURE_ARG_POINTER(aURI);
   2608 
   2609  nsAutoCString spec;
   2610  nsresult rv = aURI->GetSpec(spec);
   2611  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   2612 
   2613  // Create a sensibly named filename for the URI and store in the URI map
   2614  URIData* data;
   2615  if (mURIMap.Get(spec, &data)) {
   2616    if (aNeedsPersisting) {
   2617      data->mNeedsPersisting = true;
   2618    }
   2619    if (aData) {
   2620      *aData = data;
   2621    }
   2622    return NS_OK;
   2623  }
   2624 
   2625  // Create a unique file name for the uri
   2626  nsString filename;
   2627  rv = MakeFilenameFromURI(aURI, filename);
   2628  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   2629 
   2630  // Store the file name
   2631  data = new URIData;
   2632 
   2633  data->mContentPolicyType = aContentPolicyType;
   2634  data->mNeedsPersisting = aNeedsPersisting;
   2635  data->mNeedsFixup = true;
   2636  data->mFilename = filename;
   2637  data->mSaved = false;
   2638  data->mIsSubFrame = false;
   2639  data->mDataPath = mCurrentDataPath;
   2640  data->mDataPathIsRelative = mCurrentDataPathIsRelative;
   2641  data->mRelativePathToData = mCurrentRelativePathToData;
   2642  data->mRelativeDocumentURI = mTargetBaseURI;
   2643  data->mCharset = mCurrentCharset;
   2644 
   2645  aDoc->GetPrincipal(getter_AddRefs(data->mTriggeringPrincipal));
   2646  aDoc->GetCookieJarSettings(getter_AddRefs(data->mCookieJarSettings));
   2647 
   2648  if (aNeedsPersisting) mCurrentThingsToPersist++;
   2649 
   2650  mURIMap.InsertOrUpdate(spec, UniquePtr<URIData>(data));
   2651  if (aData) {
   2652    *aData = data;
   2653  }
   2654 
   2655  return NS_OK;
   2656 }
   2657 
   2658 // Decide if we need to apply conversion to the passed channel.
   2659 void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel* aChannel) {
   2660  nsresult rv = NS_OK;
   2661  nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aChannel, &rv);
   2662  if (NS_FAILED(rv)) return;
   2663 
   2664  // Set the default conversion preference:
   2665  encChannel->SetApplyConversion(false);
   2666 
   2667  nsCOMPtr<nsIURI> thisURI;
   2668  aChannel->GetURI(getter_AddRefs(thisURI));
   2669  nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(thisURI));
   2670  if (!sourceURL) return;
   2671  nsAutoCString extension;
   2672  sourceURL->GetFileExtension(extension);
   2673 
   2674  nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
   2675  encChannel->GetContentEncodings(getter_AddRefs(encEnum));
   2676  if (!encEnum) return;
   2677  nsCOMPtr<nsIExternalHelperAppService> helperAppService =
   2678      do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
   2679  if (NS_FAILED(rv)) return;
   2680  bool hasMore;
   2681  rv = encEnum->HasMore(&hasMore);
   2682  if (NS_SUCCEEDED(rv) && hasMore) {
   2683    nsAutoCString encType;
   2684    rv = encEnum->GetNext(encType);
   2685    if (NS_SUCCEEDED(rv)) {
   2686      bool applyConversion = false;
   2687      rv = helperAppService->ApplyDecodingForExtension(extension, encType,
   2688                                                       &applyConversion);
   2689      if (NS_SUCCEEDED(rv)) encChannel->SetApplyConversion(applyConversion);
   2690    }
   2691  }
   2692 }