| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/offline_pages/offline_page_mhtml_archiver.h" |
| |
| #include <stdint.h> |
| #include <memory> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "components/offline_pages/core/client_namespace_constants.h" |
| #include "components/offline_pages/core/model/offline_page_model_utils.h" |
| #include "components/offline_pages/core/offline_clock.h" |
| #include "components/offline_pages/core/test_scoped_offline_clock.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace offline_pages { |
| |
| namespace { |
| |
| const char kTestURL[] = "http://example.com/hello.mhtml"; |
| const char kNonExistentURL[] = "http://example.com/non_existent.mhtml"; |
| // Size of chrome/test/data/offline_pages/hello.mhtml |
| const int64_t kTestFileSize = 471LL; |
| const base::string16 kTestTitle = base::UTF8ToUTF16("a title"); |
| // SHA256 Hash of chrome/test/data/offline_pages/hello.mhtml |
| const std::string kTestDigest( |
| "\x43\x60\x62\x02\x06\x15\x0f\x3e\x77\x99\x3d\xed\xdc\xd4\xe2\x0d\xbe\xbd" |
| "\x77\x1a\xfb\x32\x00\x51\x7e\x63\x7d\x3b\x2e\x46\x63\xf6", |
| 32); |
| |
| constexpr base::TimeDelta kTimeToSaveMhtml = |
| base::TimeDelta::FromMilliseconds(1000); |
| constexpr base::TimeDelta kTimeToComputeDigest = |
| base::TimeDelta::FromMilliseconds(10); |
| |
| class TestMHTMLArchiver : public OfflinePageMHTMLArchiver { |
| public: |
| enum class TestScenario { |
| SUCCESS, |
| NOT_ABLE_TO_ARCHIVE, |
| WEB_CONTENTS_MISSING, |
| CONNECTION_SECURITY_ERROR, |
| ERROR_PAGE, |
| INTERSTITIAL_PAGE, |
| }; |
| |
| TestMHTMLArchiver(const GURL& url, |
| const TestScenario test_scenario, |
| TestScopedOfflineClock* clock); |
| ~TestMHTMLArchiver() override; |
| |
| private: |
| void GenerateMHTML(const base::FilePath& archives_dir, |
| content::WebContents* web_contents, |
| const CreateArchiveParams& create_archive_params) override; |
| bool HasConnectionSecurityError(content::WebContents* web_contents) override; |
| content::PageType GetPageType(content::WebContents* web_contents) override; |
| |
| const GURL url_; |
| const TestScenario test_scenario_; |
| // Not owned. |
| TestScopedOfflineClock* clock_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestMHTMLArchiver); |
| }; |
| |
| TestMHTMLArchiver::TestMHTMLArchiver(const GURL& url, |
| const TestScenario test_scenario, |
| TestScopedOfflineClock* clock) |
| : url_(url), test_scenario_(test_scenario), clock_(clock) {} |
| |
| TestMHTMLArchiver::~TestMHTMLArchiver() { |
| } |
| |
| void TestMHTMLArchiver::GenerateMHTML( |
| const base::FilePath& archives_dir, |
| content::WebContents* web_contents, |
| const CreateArchiveParams& create_archive_params) { |
| if (test_scenario_ == TestScenario::WEB_CONTENTS_MISSING) { |
| ReportFailure(ArchiverResult::ERROR_CONTENT_UNAVAILABLE); |
| return; |
| } |
| |
| if (test_scenario_ == TestScenario::NOT_ABLE_TO_ARCHIVE) { |
| ReportFailure(ArchiverResult::ERROR_ARCHIVE_CREATION_FAILED); |
| return; |
| } |
| |
| EXPECT_EQ(kDownloadNamespace, create_archive_params.name_space); |
| base::FilePath archive_file_path = |
| archives_dir.AppendASCII(url_.ExtractFileName()); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&TestMHTMLArchiver::OnGenerateMHTMLDone, |
| base::Unretained(this), url_, archive_file_path, |
| kTestTitle, create_archive_params.name_space, |
| OfflineTimeNow(), kTestFileSize)); |
| |
| clock_->Advance(kTimeToSaveMhtml); |
| } |
| |
| bool TestMHTMLArchiver::HasConnectionSecurityError( |
| content::WebContents* web_contents) { |
| return test_scenario_ == TestScenario::CONNECTION_SECURITY_ERROR; |
| } |
| |
| content::PageType TestMHTMLArchiver::GetPageType( |
| content::WebContents* web_contents) { |
| if (test_scenario_ == TestScenario::ERROR_PAGE) |
| return content::PageType::PAGE_TYPE_ERROR; |
| if (test_scenario_ == TestScenario::INTERSTITIAL_PAGE) |
| return content::PageType::PAGE_TYPE_INTERSTITIAL; |
| return content::PageType::PAGE_TYPE_NORMAL; |
| } |
| |
| } // namespace |
| |
| class OfflinePageMHTMLArchiverTest : public testing::Test { |
| public: |
| // Histogram names checked for within this test, already appended with the |
| // offline pages namespace used in all |CreateArchive| calls. |
| const std::string kCreateArchiveTimeHistogram = |
| model_utils::AddHistogramSuffix( |
| kDownloadNamespace, |
| "OfflinePages.SavePage.CreateArchiveTime"); |
| const std::string kComputeDigestTimeHistogram = |
| model_utils::AddHistogramSuffix( |
| kDownloadNamespace, |
| "OfflinePages.SavePage.ComputeDigestTime"); |
| |
| OfflinePageMHTMLArchiverTest(); |
| ~OfflinePageMHTMLArchiverTest() override; |
| |
| void SetUp() override; |
| |
| // Creates an archiver for testing scenario and uses it to create an archive. |
| void CreateArchive(const GURL& url, TestMHTMLArchiver::TestScenario scenario); |
| |
| // Test tooling methods. |
| void PumpLoop(); |
| void WaitForAsyncOperation(); |
| |
| base::FilePath GetTestFilePath(const GURL& url) const { |
| return archive_dir_path_.AppendASCII(url.ExtractFileName()); |
| } |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| OfflinePageArchiver::ArchiverResult last_result() const { |
| return last_result_; |
| } |
| const base::FilePath& last_file_path() const { return last_file_path_; } |
| int64_t last_file_size() const { return last_file_size_; } |
| const std::string& last_digest() const { return last_digest_; } |
| |
| OfflinePageArchiver::CreateArchiveCallback callback() { |
| return base::BindOnce(&OfflinePageMHTMLArchiverTest::OnCreateArchiveDone, |
| base::Unretained(this)); |
| } |
| |
| private: |
| void OnCreateArchiveDone(OfflinePageArchiver::ArchiverResult result, |
| const GURL& url, |
| const base::FilePath& file_path, |
| const base::string16& title, |
| int64_t file_size, |
| const std::string& digest); |
| |
| content::TestBrowserThreadBundle thread_bundle_; |
| base::FilePath archive_dir_path_; |
| base::HistogramTester histogram_tester_; |
| |
| OfflinePageArchiver::ArchiverResult last_result_; |
| GURL last_url_; |
| base::FilePath last_file_path_; |
| int64_t last_file_size_; |
| std::string last_digest_; |
| bool async_operation_completed_ = false; |
| base::Closure async_operation_completed_callback_; |
| |
| TestScopedOfflineClock clock_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OfflinePageMHTMLArchiverTest); |
| }; |
| |
| OfflinePageMHTMLArchiverTest::OfflinePageMHTMLArchiverTest() |
| : thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD), |
| last_result_(OfflinePageArchiver::ArchiverResult::ERROR_DEVICE_FULL), |
| last_file_size_(0L) {} |
| |
| OfflinePageMHTMLArchiverTest::~OfflinePageMHTMLArchiverTest() { |
| } |
| |
| void OfflinePageMHTMLArchiverTest::SetUp() { |
| base::FilePath test_data_dir_path; |
| ASSERT_TRUE( |
| base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_path)); |
| archive_dir_path_ = test_data_dir_path.AppendASCII("offline_pages"); |
| clock_.SetNow(base::Time::Now()); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::CreateArchive( |
| const GURL& url, |
| TestMHTMLArchiver::TestScenario scenario) { |
| TestMHTMLArchiver archiver(url, scenario, &clock_); |
| archiver.CreateArchive( |
| archive_dir_path_, |
| OfflinePageArchiver::CreateArchiveParams(kDownloadNamespace), nullptr, |
| callback()); |
| PumpLoop(); |
| clock_.Advance(kTimeToComputeDigest); |
| WaitForAsyncOperation(); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::OnCreateArchiveDone( |
| OfflinePageArchiver::ArchiverResult result, |
| const GURL& url, |
| const base::FilePath& file_path, |
| const base::string16& title, |
| int64_t file_size, |
| const std::string& digest) { |
| DCHECK(!async_operation_completed_); |
| async_operation_completed_ = true; |
| last_url_ = url; |
| last_result_ = result; |
| last_file_path_ = file_path; |
| last_file_size_ = file_size; |
| last_digest_ = digest; |
| if (!async_operation_completed_callback_.is_null()) |
| async_operation_completed_callback_.Run(); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::PumpLoop() { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::WaitForAsyncOperation() { |
| // No need to wait if async operation is not needed. |
| if (async_operation_completed_) |
| return; |
| base::RunLoop run_loop; |
| async_operation_completed_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| // Tests that creation of an archiver fails when web contents is missing. |
| TEST_F(OfflinePageMHTMLArchiverTest, WebContentsMissing) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, |
| TestMHTMLArchiver::TestScenario::WEB_CONTENTS_MISSING); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_CONTENT_UNAVAILABLE, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| } |
| |
| // Tests for archiver failing save an archive. |
| TEST_F(OfflinePageMHTMLArchiverTest, NotAbleToGenerateArchive) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::NOT_ABLE_TO_ARCHIVE); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_ARCHIVE_CREATION_FAILED, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| histogram_tester()->ExpectTotalCount(kCreateArchiveTimeHistogram, 0); |
| histogram_tester()->ExpectTotalCount(kComputeDigestTimeHistogram, 0); |
| } |
| |
| // Tests for archiver handling of non-secure connection. |
| TEST_F(OfflinePageMHTMLArchiverTest, ConnectionNotSecure) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, |
| TestMHTMLArchiver::TestScenario::CONNECTION_SECURITY_ERROR); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_SECURITY_CERTIFICATE, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| histogram_tester()->ExpectTotalCount(kCreateArchiveTimeHistogram, 0); |
| histogram_tester()->ExpectTotalCount(kComputeDigestTimeHistogram, 0); |
| } |
| |
| // Tests for archiver handling of an error page. |
| TEST_F(OfflinePageMHTMLArchiverTest, PageError) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::ERROR_PAGE); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_ERROR_PAGE, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| histogram_tester()->ExpectTotalCount(kCreateArchiveTimeHistogram, 0); |
| histogram_tester()->ExpectTotalCount(kComputeDigestTimeHistogram, 0); |
| } |
| |
| // Tests for archiver handling of an interstitial page. |
| TEST_F(OfflinePageMHTMLArchiverTest, InterstitialPage) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::INTERSTITIAL_PAGE); |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_INTERSTITIAL_PAGE, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| histogram_tester()->ExpectTotalCount(kCreateArchiveTimeHistogram, 0); |
| histogram_tester()->ExpectTotalCount(kComputeDigestTimeHistogram, 0); |
| } |
| |
| // Tests for failing to compute digest for archive file. |
| TEST_F(OfflinePageMHTMLArchiverTest, DigestError) { |
| GURL page_url = GURL(kNonExistentURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::SUCCESS); |
| |
| EXPECT_EQ( |
| OfflinePageArchiver::ArchiverResult::ERROR_DIGEST_CALCULATION_FAILED, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| histogram_tester()->ExpectUniqueSample(kCreateArchiveTimeHistogram, |
| kTimeToSaveMhtml.InMilliseconds(), 1); |
| histogram_tester()->ExpectTotalCount(kComputeDigestTimeHistogram, 0); |
| } |
| |
| // Tests for successful creation of the offline page archive. |
| TEST_F(OfflinePageMHTMLArchiverTest, SuccessfullyCreateOfflineArchive) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::SUCCESS); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::SUCCESSFULLY_CREATED, |
| last_result()); |
| EXPECT_EQ(GetTestFilePath(page_url), last_file_path()); |
| EXPECT_EQ(kTestFileSize, last_file_size()); |
| EXPECT_EQ(kTestDigest, last_digest()); |
| histogram_tester()->ExpectUniqueSample(kCreateArchiveTimeHistogram, |
| kTimeToSaveMhtml.InMilliseconds(), 1); |
| histogram_tester()->ExpectUniqueSample( |
| kComputeDigestTimeHistogram, kTimeToComputeDigest.InMilliseconds(), 1); |
| } |
| |
| } // namespace offline_pages |