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 }