chromium/chrome/browser/download/save_page_browsertest.cc

// 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.

#include <stddef.h>
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/repeating_test_future.h"
#include "base/test/test_file_util.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/download/download_history.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/save_package_file_picker.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/download/public/common/download_item.h"
#include "components/history/core/browser/download_constants.h"
#include "components/history/core/browser/download_row.h"
#include "components/prefs/pref_member.h"
#include "components/prefs/pref_service.h"
#include "components/security_state/core/security_state.h"
#include "components/services/quarantine/test_support.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "net/base/filename_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/shell_dialogs/fake_select_file_dialog.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/dbus/dlp/dlp_client.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

BrowserContext;
BrowserThread;
DownloadManager;
RenderFrameHost;
RenderProcessHost;
WebContents;
DownloadItem;
_;
ContainsRegex;
HasSubstr;
FakeSelectFileDialog;

namespace {

// Returns file contents with each continuous run of whitespace replaced by a
// single space.
std::string ReadFileAndCollapseWhitespace(const base::FilePath& file_path) {}

// Takes a string with "url=(%04d)%s", and replaces that with the length and
// contents of the path the response was saved from, |url|, to match output by
// the SavePageAs logic.
std::string WriteSavedFromPath(const std::string& file_contents,
                               const GURL& url) {}

// Waits for an item record in the downloads database to match |filter|. See
// DownloadStoredProperly() below for an example filter.
class DownloadPersistedObserver : public DownloadHistory::Observer {};

// Waits for an item record to be removed from the downloads database.
class DownloadRemovedObserver : public DownloadPersistedObserver {};

bool DownloadStoredProperly(const GURL& expected_url,
                            const base::FilePath& expected_path,
                            int64_t num_files,
                            history::DownloadState expected_state,
                            DownloadItem* item,
                            const history::DownloadRow& info) {}

static const char kAppendedExtension[] =;

// Loosely based on logic in DownloadTestObserver.
class DownloadItemCreatedObserver : public DownloadManager::Observer {};

class SavePageBrowserTest : public InProcessBrowserTest {};

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveHTMLOnly) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveFileURL) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest,
                       SaveHTMLOnly_CrossOriginReadPolicy) {}

// TODO(crbug.com/40805571): Flaky on mac arm64.
#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
#define MAYBE_SaveHTMLOnlyCancel
#else
#define MAYBE_SaveHTMLOnlyCancel
#endif
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, MAYBE_SaveHTMLOnlyCancel) {}

// Test that saving an HTML file with long (i.e. > 65536 bytes) text content
// does not crash the browser despite the renderer requiring more than one
// "pass" to serialize the HTML content (see crash from crbug.com/1085721).
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveHTMLWithLongTextContent) {}

class DelayingDownloadManagerDelegate : public ChromeDownloadManagerDelegate {};

// Disabled on multiple platforms due to flakiness. crbug.com/580766
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, DISABLED_SaveHTMLOnlyTabDestroy) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveViewSourceHTMLOnly) {}

// Regression test for https://crbug.com/974312 (saving a page that was served
// with `Cross-Origin-Resource-Policy: same-origin` http response header).
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveCompleteHTML) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest,
                       SaveDuringInitialNavigationIncognito) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, NoSave) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, FileNameFromPageTitle) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, RemoveFromList) {}

// This tests that a webpage with the title "test.exe" is saved as
// "test.exe.htm".
// We probably don't care to handle this on Linux or Mac.
#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, CleanFilenameFromPageTitle) {
  base::FilePath download_dir =
      DownloadPrefs::FromDownloadManager(GetDownloadManager())->
          DownloadPath();
  base::FilePath full_file_name =
      download_dir.AppendASCII(std::string("test.exe") + kAppendedExtension);
  base::FilePath dir = download_dir.AppendASCII("test.exe_files");

  base::ScopedAllowBlockingForTesting allow_blocking;
  EXPECT_FALSE(base::PathExists(full_file_name));
  GURL url = embedded_test_server()->GetURL("/save_page/c.htm");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  SavePackageFilePicker::SetShouldPromptUser(false);
  base::RunLoop run_loop;
  content::SavePackageFinishedObserver observer(
      browser()->profile()->GetDownloadManager(), run_loop.QuitClosure());
  chrome::SavePage(browser());
  run_loop.Run();

  EXPECT_TRUE(base::PathExists(full_file_name));

  EXPECT_TRUE(base::DieFileDie(full_file_name, false));
  EXPECT_TRUE(base::DieFileDie(dir, true));
}
#endif

// Tests that the SecurityLevel histograms are logged for save page downloads.
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SecurityLevelHistogram) {}

// Tests that a page can be saved as MHTML.
// Flaky on Windows, crbug.com/1048100
#if BUILDFLAG(IS_WIN)
#define MAYBE_SavePageAsMHTML
#else
#define MAYBE_SavePageAsMHTML
#endif
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, MAYBE_SavePageAsMHTML) {}

// Tests that if we default our file picker to MHTML due to user preference we
// update the suggested file name to end with .mhtml.
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest,
                       SavePageAsMHTMLByPrefUpdatesExtension) {}

// Flaky on Windows: https://crbug.com/1247404.
#if BUILDFLAG(IS_WIN)
#define MAYBE_SavePageBrowserTest_NonMHTML
#else
#define MAYBE_SavePageBrowserTest_NonMHTML
#endif
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest,
                       MAYBE_SavePageBrowserTest_NonMHTML) {}

// If a save-page-complete operation results in creating subresources that would
// otherwise be considered dangerous, such files should get a .download
// extension appended so that they won't be accidentally executed by the user.
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, DangerousSubresources) {}

// Test that we don't crash when the page contains an iframe that
// was handled as a download (http://crbug.com/42212).
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveDownloadableIFrame) {}

// Test that file: URI won't be saved when referred to from an HTTP page.
// See also https://crbug.com/616429.
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveUnauthorizedResource) {}

#if BUILDFLAG(IS_WIN)
// Save a file and confirm that the file is correctly quarantined.
IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveURLQuarantine) {
  GURL url = embedded_test_server()->GetURL("/save_page/text.txt");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  base::FilePath full_file_name, dir;
  SaveCurrentTab(url, content::SAVE_PAGE_TYPE_AS_ONLY_HTML, "test", 1, &dir,
                 &full_file_name);
  ASSERT_FALSE(HasFailure());

  base::ScopedAllowBlockingForTesting allow_blocking;
  EXPECT_TRUE(base::PathExists(full_file_name));
  EXPECT_FALSE(base::PathExists(dir));
  EXPECT_TRUE(base::ContentsEqual(GetTestDirFile("text.txt"), full_file_name));
  EXPECT_TRUE(quarantine::IsFileQuarantined(full_file_name, url, GURL()));
}
#endif

// Test suite that allows testing --site-per-process against cross-site frames.
// See http://dev.chromium.org/developers/design-documents/site-isolation.
class SavePageSitePerProcessBrowserTest : public SavePageBrowserTest {};

// Test for crbug.com/526786.
IN_PROC_BROWSER_TEST_F(SavePageSitePerProcessBrowserTest, SaveAsCompleteHtml) {}

// Test for crbug.com/538766.
// Disabled on Mac due to excessive flakiness. https://crbug.com/1271741
#if BUILDFLAG(IS_MAC)
#define MAYBE_SaveAsMHTML
#else
#define MAYBE_SaveAsMHTML
#endif
IN_PROC_BROWSER_TEST_F(SavePageSitePerProcessBrowserTest,
                       MAYBE_SaveAsMHTML) {}

// Test for crbug.com/541342 - handling of dead renderer processes.
IN_PROC_BROWSER_TEST_F(SavePageSitePerProcessBrowserTest,
                       CompleteHtmlWhenRendererIsDead) {}

// Test suite that verifies that the frame tree "looks" the same before
// and after a save-page-as.
class SavePageOriginalVsSavedComparisonTest
    : public SavePageSitePerProcessBrowserTest,
      public ::testing::WithParamInterface<content::SavePageType> {};

// Test coverage for:
// - crbug.com/526786: OOPIFs support for CompleteHtml
// - crbug.com/538766: OOPIFs support for MHTML
// - crbug.com/539936: Subframe gets redirected.
// Test compares original-vs-saved for a page with cross-site frames
// (subframes get redirected to a different domain - see frames-xsite.htm).
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, CrossSite) {}

// Test compares original-vs-saved for a page with <object> elements.
// (see crbug.com/553478).
// crbug.com/1070886: disabled because of flakiness.
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
                       DISABLED_ObjectElementsViaHttp) {}

// Tests that saving a page from file: URI works.
// TODO(lukasza): https://crbug.com/964364: Re-enable the test.
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
                       DISABLED_ObjectElementsViaFile) {}

// Test compares original-vs-saved for a page with frames at about:blank uri.
// This tests handling of iframe elements without src attribute (only with
// srcdoc attribute) and how they get saved / cross-referenced.
#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40202613): Fails on dcheck-enabled builds on 11.0.
#define MAYBE_AboutBlank
#else
#define MAYBE_AboutBlank
#endif
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
                       MAYBE_AboutBlank) {}

// Test compares original-vs-saved for a page with nested frames.
// Two levels of nesting are especially good for verifying correct
// link rewriting for subframes-vs-main-frame (see crbug.com/554666).
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, NestedFrames) {}

// Test for crbug.com/106364 and crbug.com/538188.
// Test frames have the same uri ...
//   subframe1 and subframe2 - both have src=b.htm
//   subframe3 and subframe4 - about:blank (no src, only srcdoc attribute).
// ... but different content (generated by main frame's javascript).
#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40202613): Fails on dcheck-enabled builds on 11.0.
#define MAYBE_RuntimeChanges
#else
#define MAYBE_RuntimeChanges
#endif
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
                       MAYBE_RuntimeChanges) {}

// Test for saving frames with various encodings:
// - iso-8859-2: encoding declared via <meta> element
// - utf16-le-bom.htm, utf16-be-bom.htm: encoding detected via BOM
// - utf16-le-nobom.htm, utf16-le-nobom.htm - encoding declared via
//                                            mocked http headers
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, Encoding) {}

// Test for saving style element and attribute (see also crbug.com/568293).
#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40202613): Fails on dcheck-enabled builds on 11.0.
#define MAYBE_Style
#else
#define MAYBE_Style
#endif
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, MAYBE_Style) {}

// Test for saving a page with broken subresources:
// - Broken, undecodable image (see also https://crbug.com/586680)
// - Broken link, to unresolvable host (see also https://crbug.com/594219)
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, BrokenImage) {}

// Test for saving a page with a cross-site <object> element.
// Disabled on Windows due to flakiness. crbug.com/1070597.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#define MAYBE_CrossSiteObject
#else
#define MAYBE_CrossSiteObject
#endif
IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest,
                       MAYBE_CrossSiteObject) {}

INSTANTIATE_TEST_SUITE_P();
INSTANTIATE_TEST_SUITE_P();

class BlockingDownloadManagerDelegate : public ChromeDownloadManagerDelegate {};

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveOnlyHTMLBlocked) {}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveCompleteHTMLBlocked) {}

#if BUILDFLAG(IS_CHROMEOS)

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveHTMLWithDlp) {
  base::FilePath full_file_name, dir;
  GURL url;

  chromeos::DlpClient::Shutdown();
  chromeos::DlpClient::InitializeFake();
  base::test::RepeatingTestFuture<
      dlp::AddFilesRequest, base::OnceCallback<void(dlp::AddFilesResponse)>>
      add_file_cb;
  chromeos::DlpClient::Get()->GetTestInterface()->SetAddFilesMock(
      add_file_cb.GetCallback());

  url = NavigateToMockURL("a");

  SaveCurrentTab(url, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML, "a", 1, &dir,
                 &full_file_name);

  ASSERT_FALSE(HasFailure());

  auto request = std::get<0>(add_file_cb.Take());
  EXPECT_EQ(1, request.add_file_requests().size());
  EXPECT_EQ(full_file_name.value(), request.add_file_requests(0).file_path());
  EXPECT_EQ(request.add_file_requests(0).source_url(), url.spec());

  base::ScopedAllowBlockingForTesting allow_blocking;
  EXPECT_TRUE(base::PathExists(full_file_name));
  EXPECT_FALSE(base::PathExists(dir));
}

IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SaveMHTMLWithDlp) {
  base::FilePath full_file_name, dir;
  GURL url;

  chromeos::DlpClient::Shutdown();
  chromeos::DlpClient::InitializeFake();
  base::test::RepeatingTestFuture<
      dlp::AddFilesRequest, base::OnceCallback<void(dlp::AddFilesResponse)>>
      add_file_cb;
  chromeos::DlpClient::Get()->GetTestInterface()->SetAddFilesMock(
      add_file_cb.GetCallback());

  url = NavigateToMockURL("a");

  SaveCurrentTab(url, content::SAVE_PAGE_TYPE_AS_MHTML, "a", -1, &dir,
                 &full_file_name);

  ASSERT_FALSE(HasFailure());

  auto request = std::get<0>(add_file_cb.Take());
  EXPECT_EQ(1, request.add_file_requests().size());
  EXPECT_EQ(full_file_name.value(), request.add_file_requests(0).file_path());
  EXPECT_EQ(request.add_file_requests(0).source_url(), url.spec());

  base::ScopedAllowBlockingForTesting allow_blocking;
  EXPECT_TRUE(base::PathExists(full_file_name));
  EXPECT_FALSE(base::PathExists(dir));
}

#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace