// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifdef UNSAFE_BUFFERS_BUILD // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. #pragma allow_unsafe_buffers #endif #include "components/download/public/common/download_item_impl.h" #include <stdint.h> #include <iterator> #include <map> #include <memory> #include <utility> #include <vector> #include "base/containers/circular_deque.h" #include "base/containers/queue.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" #include "base/strings/string_number_conversions.h" #include "base/task/single_thread_task_runner.h" #include "base/test/gmock_move_support.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "build/build_config.h" #include "components/download/public/common/download_create_info.h" #include "components/download/public/common/download_destination_observer.h" #include "components/download/public/common/download_features.h" #include "components/download/public/common/download_file_factory.h" #include "components/download/public/common/download_interrupt_reasons.h" #include "components/download/public/common/download_item_impl_delegate.h" #include "components/download/public/common/download_target_info.h" #include "components/download/public/common/download_url_parameters.h" #include "components/download/public/common/mock_download_file.h" #include "crypto/secure_hash.h" #include "net/http/http_response_headers.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" _; ByMove; DoAll; Invoke; InvokeWithoutArgs; NiceMock; Property; Return; ReturnRefOfCopy; SaveArg; StrictMock; WithArg; const int kDownloadChunkSize = …; const int kDownloadSpeed = …; const base::FilePath::CharType kDummyTargetPath[] = …); const base::FilePath::CharType kDummyIntermediatePath[] = …); namespace download { namespace { template <typename T> base::HistogramBase::Sample ToHistogramSample(T t) { … } class MockDelegate : public DownloadItemImplDelegate { … }; class TestDownloadItemObserver : public DownloadItem::Observer { … }; // Schedules a task to invoke a callback that's bound to the specified // parameter. // E.g.: // // EXPECT_CALL(foo, Bar(1, _)) // .WithArg<1>(ScheduleCallbackWithParams(0, 0, task_runner)); // // .. will invoke the second argument to Bar with 0 as the parameter. ACTION_P3(ScheduleCallbackWithParams, param1, param2, task_runner) { … } const char kTestData1[] = …; // SHA256 hash of TestData1 const uint8_t kHashOfTestData1[] = …; class DownloadItemTest : public testing::Test { … }; // Tests to ensure calls that change a DownloadItem generate an update // to observers. State changing functions not tested: // void OpenDownload(); // void ShowDownloadInShell(); // void CompleteDelayedDownload(); // set_* mutators TEST_F(DownloadItemTest, NotificationAfterUpdate) { … } TEST_F(DownloadItemTest, NotificationAfterCancel) { … } TEST_F(DownloadItemTest, NotificationAfterComplete) { … } TEST_F(DownloadItemTest, NotificationAfterDownloadedFileRemoved) { … } TEST_F(DownloadItemTest, NotificationAfterInterrupted) { … } TEST_F(DownloadItemTest, NotificationAfterDestroyed) { … } TEST_F(DownloadItemTest, NotificationAfterRemove) { … } TEST_F(DownloadItemTest, NotificationAfterOnContentCheckCompleted) { … } // DownloadItemImpl::OnDownloadTargetDetermined will schedule a task to run // DownloadFile::Rename(). Once the rename // completes, DownloadItemImpl receives a notification with the new file // name. Check that observers are updated when the new filename is available and // not before. TEST_F(DownloadItemTest, NotificationAfterOnDownloadTargetDetermined) { … } TEST_F(DownloadItemTest, NotificationAfterTogglePause) { … } // Test that a download is resumed automatically after a continuable interrupt. TEST_F(DownloadItemTest, AutomaticResumption_Continue) { … } // Automatic resumption should restart and discard the intermediate file if the // interrupt reason requires it. TEST_F(DownloadItemTest, AutomaticResumption_Restart) { … } // Test that automatic resumption doesn't happen after an interrupt that // requires user action to resolve. TEST_F(DownloadItemTest, AutomaticResumption_NeedsUserAction) { … } // Test that a download is resumed automatically after a content length mismatch // error. TEST_F(DownloadItemTest, AutomaticResumption_ContentLengthMismatch) { … } // Check we do correct cleanup for RESUME_MODE_INVALID interrupts. TEST_F(DownloadItemTest, UnresumableInterrupt) { … } TEST_F(DownloadItemTest, AutomaticResumption_AttemptLimit) { … } // If the download attempts to resume and the resumption request fails, the // subsequent Start() call shouldn't update the origin state (URL redirect // chains, Content-Disposition, download URL, etc..) TEST_F(DownloadItemTest, FailedResumptionDoesntUpdateOriginState) { … } // If the download resumption request succeeds, the origin state should be // updated. TEST_F(DownloadItemTest, SucceededResumptionUpdatesOriginState) { … } // Ensure when strong validators changed on resumption, the received // slices should be cleared. TEST_F(DownloadItemTest, ClearReceivedSliceIfEtagChanged) { … } // Ensure when a network socket error happens on resumption, the received slices // info should be kept if the download is not restarted from beginning, so the // download progress will not move backward. TEST_F(DownloadItemTest, KeepReceivedSliceIfNetworkError) { … } // Test that resumption uses the final URL in a URL chain when resuming. TEST_F(DownloadItemTest, ResumeUsesFinalURL) { … } TEST_F(DownloadItemTest, DisplayName) { … } // Test to make sure that Start method calls DF initialize properly. TEST_F(DownloadItemTest, Start) { … } // Download file and the request should be cancelled as a result of download // file initialization failing. TEST_F(DownloadItemTest, InitDownloadFileFails) { … } // Handling of downloads initiated via a failed request. In this case, Start() // will get called with a DownloadCreateInfo with a non-zero interrupt_reason. TEST_F(DownloadItemTest, StartFailedDownload) { … } // Test that the delegate is invoked after the download file is renamed. TEST_F(DownloadItemTest, CallbackAfterRename) { … } // Test that the delegate is invoked after the download file is renamed and the // download item is in an interrupted state. TEST_F(DownloadItemTest, CallbackAfterInterruptedRename) { … } TEST_F(DownloadItemTest, Interrupted) { … } // Destination errors that occur before the intermediate rename shouldn't cause // the download to be marked as interrupted until after the intermediate rename. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) { … } // As above. But if the download can be resumed by continuing, then the // intermediate path should be retained when the download is interrupted after // the intermediate rename succeeds. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) { … } // As above. If the intermediate rename fails, then the interrupt reason should // be set to the file error and the intermediate path should be empty. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) { … } TEST_F(DownloadItemTest, Canceled) { … } TEST_F(DownloadItemTest, DownloadTargetDetermined_Cancel) { … } TEST_F(DownloadItemTest, DownloadTargetDetermined_CancelWithEmptyName) { … } TEST_F(DownloadItemTest, DownloadTargetDetermined_Conflict) { … } TEST_F(DownloadItemTest, DownloadTargetDetermined_NewMimeType) { … } TEST_F(DownloadItemTest, FileRemoved) { … } TEST_F(DownloadItemTest, DestinationUpdate) { … } TEST_F(DownloadItemTest, DestinationError_NoRestartRequired) { … } TEST_F(DownloadItemTest, DestinationError_RestartRequired) { … } TEST_F(DownloadItemTest, DestinationCompleted) { … } TEST_F(DownloadItemTest, EnabledActionsForNormalDownload) { … } TEST_F(DownloadItemTest, EnabledActionsForTemporaryDownload) { … } TEST_F(DownloadItemTest, EnabledActionsForInterruptedDownload) { … } TEST_F(DownloadItemTest, EnabledActionsForCancelledDownload) { … } // Test various aspects of the delegate completion blocker. // Just allowing completion. TEST_F(DownloadItemTest, CompleteDelegate_ReturnTrue) { … } // Just delaying completion. TEST_F(DownloadItemTest, CompleteDelegate_BlockOnce) { … } // Delay and set danger. TEST_F(DownloadItemTest, CompleteDelegate_SetDanger) { … } // Just delaying completion twice. TEST_F(DownloadItemTest, CompleteDelegate_BlockTwice) { … } TEST_F(DownloadItemTest, StealDangerousDownloadAndDiscard) { … } TEST_F(DownloadItemTest, StealDangerousDownloadAndKeep) { … } TEST_F(DownloadItemTest, StealInterruptedContinuableDangerousDownload) { … } TEST_F(DownloadItemTest, StealInterruptedNonContinuableDangerousDownload) { … } // Tests that for an incognito download, the target file is annotated with an // empty source URL. TEST_F(DownloadItemTest, AnnotationWithEmptyURLInIncognito) { … } // The DownloadItemDestinationUpdateRaceTest fixture (defined below) is used to // test for race conditions between download destination events received via the // DownloadDestinationObserver interface, and the target determination logic. // // The general control flow for DownloadItemImpl looks like this: // // * Start() called, which in turn calls DownloadFile::Initialize(). // // Even though OnDownloadFileInitialized hasn't been called, there could now // be destination observer calls queued prior to the task that calls // OnDownloadFileInitialized. Let's call this point in the workflow "A". // // * DownloadItemImpl::OnDownloadFileInitialized() called. // // * Assuming the result is successful, DII now invokes the delegate's // DetermineDownloadTarget method. // // At this point DownloadFile acts as the source of // DownloadDestinationObserver events, and may invoke callbacks. Let's call // this point in the workflow "B". // // * DII::OnDownloadTargetDetermined() invoked after delegate is done with // target determination. // // * DII attempts to rename the DownloadFile to its intermediate name. // // More DownloadDestinationObserver events can happen here. Let's call this // point in the workflow "C". // // * DII::OnDownloadRenamedToIntermediateName() invoked. Assuming all went well, // DII is now in IN_PROGRESS state. // // More DownloadDestinationObserver events can happen here. Let's call this // point in the workflow "D". // // The DownloadItemDestinationUpdateRaceTest works by generating various // combinations of DownloadDestinationObserver events that might occur at the // points "A", "B", "C", and "D" above. Each test in this suite cranks a // DownloadItemImpl through the states listed above and invokes the events // assigned to each position. // This type of callback represents a call to a DownloadDestinationObserver // method that's missing the DownloadDestinationObserver object. Currying this // way allows us to bind a call prior to constructing the object on which the // method would be invoked. This is necessary since we are going to construct // various permutations of observer calls that will then be applied to a // DownloadItem in a state as yet undetermined. CurriedObservation; // A list of observations that are to be made during some event in the // DownloadItemImpl control flow. Ordering of the observations is significant. ObservationList; // An ordered list of events. // // An "event" in this context refers to some stage in the DownloadItemImpl's // workflow described as "A", "B", "C", or "D" above. An EventList is expected // to always contains kEventCount events. EventList; // Number of events in an EventList. This is always 4 for now as described // above. const int kEventCount = …; // The following functions help us with currying the calls to // DownloadDestinationObserver. If std::bind was allowed along with // std::placeholders, it is possible to avoid these functions, but currently // Chromium doesn't allow using std::bind for good reasons. void DestinationUpdateInvoker( int64_t bytes_so_far, int64_t bytes_per_sec, base::WeakPtr<DownloadDestinationObserver> observer) { … } void DestinationErrorInvoker( DownloadInterruptReason reason, int64_t bytes_so_far, base::WeakPtr<DownloadDestinationObserver> observer) { … } void DestinationCompletedInvoker( int64_t total_bytes, base::WeakPtr<DownloadDestinationObserver> observer) { … } // Given a set of observations (via the range |begin|..|end|), constructs a list // of EventLists such that: // // * There are exactly |event_count| ObservationSets in each EventList. // // * Each ObservationList in each EventList contains a subrange (possibly empty) // of observations from the input range, in the same order as the input range. // // * The ordering of the ObservationList in each EventList is such that all // observations in one ObservationList occur earlier than all observations in // an ObservationList that follows it. // // * The list of EventLists together describe all the possible ways in which the // list of observations can be distributed into |event_count| events. std::vector<EventList> DistributeObservationsIntoEvents( const std::vector<CurriedObservation>::iterator begin, const std::vector<CurriedObservation>::iterator end, int event_count) { … } // For the purpose of this tests, we are only concerned with 3 events: // // 1. Immediately after the DownloadFile is initialized. // 2. Immediately after the DownloadTargetCallback is invoked. // 3. Immediately after the intermediate file is renamed. // // We are going to take a couple of sets of DownloadDestinationObserver events // and distribute them into the three events described above. And then we are // going to invoke the observations while a DownloadItemImpl is carefully // stepped through its stages. std::vector<EventList> GenerateSuccessfulEventLists() { … } std::vector<EventList> GenerateFailingEventLists() { … } class DownloadItemDestinationUpdateRaceTest : public DownloadItemTest, public ::testing::WithParamInterface<EventList> { … }; INSTANTIATE_TEST_SUITE_P(…); INSTANTIATE_TEST_SUITE_P(…); // Run through the DII workflow but the embedder cancels the download at target // determination. TEST_P(DownloadItemDestinationUpdateRaceTest, DownloadCancelledByUser) { … } // Run through the DII workflow, but the intermediate rename fails. TEST_P(DownloadItemDestinationUpdateRaceTest, IntermediateRenameFails) { … } // Run through the DII workflow. Download file initialization, target // determination and intermediate rename all succeed. TEST_P(DownloadItemDestinationUpdateRaceTest, IntermediateRenameSucceeds) { … } } // namespace } // namespace download