blob: 9ff30e923b6fb8cd673fd1f604b18937fdc8d748 [file] [log] [blame]
// Copyright 2016 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_request_job.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "chrome/browser/offline_pages/offline_page_model_factory.h"
#include "chrome/browser/offline_pages/offline_page_request_interceptor.h"
#include "chrome/browser/offline_pages/offline_page_tab_helper.h"
#include "chrome/browser/renderer_host/chrome_navigation_ui_data.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/offline_pages/core/archive_validator.h"
#include "components/offline_pages/core/client_namespace_constants.h"
#include "components/offline_pages/core/model/offline_page_model_taskified.h"
#include "components/offline_pages/core/offline_page_metadata_store_sql.h"
#include "components/offline_pages/core/request_header/offline_page_navigation_ui_data.h"
#include "components/offline_pages/core/system_download_manager_stub.h"
#include "components/previews/core/previews_decider.h"
#include "components/previews/core/previews_experiments.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/resource_type.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/filename_util.h"
#include "net/http/http_request_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_intercepting_job_factory.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages {
namespace {
const char kPrivateOfflineFileDir[] = "offline_pages";
const char kPublicOfflineFileDir[] = "public_offline_pages";
const GURL kUrl("http://test.org/page");
const GURL kUrl2("http://test.org/another");
const base::FilePath kFilename1(FILE_PATH_LITERAL("hello.mhtml"));
const base::FilePath kFilename2(FILE_PATH_LITERAL("test.mhtml"));
const base::FilePath kNonexistentFilename(
FILE_PATH_LITERAL("nonexistent.mhtml"));
const int kFileSize1 = 450; // Real size of hello.mhtml.
const int kFileSize2 = 444; // Real size of test.mhtml.
const int kMismatchedFileSize = 99999;
const std::string kDigest1(
"\x90\x64\xF9\x7C\x94\xE5\x9E\x91\x83\x3D\x41\xB0\x36\x90\x0A\xDF\xB3\xB1"
"\x5C\x13\xBE\xB8\x35\x8C\xF6\x5B\xC4\xB5\x5A\xFC\x3A\xCC",
32); // SHA256 Hash of hello.mhtml.
const std::string kDigest2(
"\xBD\xD3\x37\x79\xDA\x7F\x4E\x6A\x16\x66\xED\x49\x67\x18\x54\x48\xC6\x8E"
"\xA1\x47\x16\xA5\x44\x45\x43\xD0\x0E\x04\x9F\x4C\x45\xDC",
32); // SHA256 Hash of test.mhtml.
const std::string kMismatchedDigest(
"\xff\x64\xF9\x7C\x94\xE5\x9E\x91\x83\x3D\x41\xB0\x36\x90\x0A\xDF\xB3\xB1"
"\x5C\x13\xBE\xB8\x35\x8C\xF6\x5B\xC4\xB5\x5A\xFC\x3A\xCC",
32); // Wrong SHA256 Hash.
const int kTabId = 1;
const int kBufSize = 1024;
const char kAggregatedRequestResultHistogram[] =
"OfflinePages.AggregatedRequestResult2";
const char kOpenFileErrorCodeHistogram[] =
"OfflinePages.RequestJob.OpenFileErrorCode";
const char kSeekFileErrorCodeHistogram[] =
"OfflinePages.RequestJob.SeekFileErrorCode";
const char kAccessEntryPointHistogram[] = "OfflinePages.AccessEntryPoint.";
const char kPageSizeAccessOfflineHistogramBase[] =
"OfflinePages.PageSizeOnAccess.Offline.";
const char kPageSizeAccessOnlineHistogramBase[] =
"OfflinePages.PageSizeOnAccess.Online.";
const int64_t kDownloadId = 42LL;
class OfflinePageRequestJobTestDelegate
: public OfflinePageRequestJob::Delegate {
public:
OfflinePageRequestJobTestDelegate(content::WebContents* web_content,
int tab_id)
: web_content_(web_content), tab_id_(tab_id) {}
content::ResourceRequestInfo::WebContentsGetter GetWebContentsGetter(
net::URLRequest* request) const override {
return base::Bind(&OfflinePageRequestJobTestDelegate::GetWebContents,
web_content_);
}
OfflinePageRequestJob::Delegate::TabIdGetter GetTabIdGetter() const override {
return base::Bind(&OfflinePageRequestJobTestDelegate::GetTabId, tab_id_);
}
private:
static content::WebContents* GetWebContents(
content::WebContents* web_content) {
return web_content;
}
static bool GetTabId(int tab_id_value,
content::WebContents* web_content,
int* tab_id) {
*tab_id = tab_id_value;
return true;
}
content::WebContents* web_content_;
int tab_id_;
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestJobTestDelegate);
};
class TestURLRequestDelegate : public net::URLRequest::Delegate {
public:
typedef base::Callback<void(const std::string&, int)> ReadCompletedCallback;
explicit TestURLRequestDelegate(const ReadCompletedCallback& callback)
: read_completed_callback_(callback),
buffer_(new net::IOBuffer(kBufSize)),
request_status_(net::ERR_IO_PENDING) {}
void OnResponseStarted(net::URLRequest* request, int net_error) override {
DCHECK_NE(net::ERR_IO_PENDING, net_error);
if (net_error != net::OK) {
OnReadCompleted(request, net_error);
return;
}
// Initiate the first read.
int bytes_read = request->Read(buffer_.get(), kBufSize);
if (bytes_read >= 0) {
OnReadCompleted(request, bytes_read);
} else if (bytes_read != net::ERR_IO_PENDING) {
request_status_ = bytes_read;
OnResponseCompleted();
}
}
void OnReadCompleted(net::URLRequest* request, int bytes_read) override {
if (bytes_read > 0)
data_received_.append(buffer_->data(), bytes_read);
// If it was not end of stream, request to read more.
while (bytes_read > 0) {
bytes_read = request->Read(buffer_.get(), kBufSize);
if (bytes_read > 0)
data_received_.append(buffer_->data(), bytes_read);
}
request_status_ = (bytes_read >= 0) ? net::OK : bytes_read;
if (bytes_read != net::ERR_IO_PENDING)
OnResponseCompleted();
}
private:
void OnResponseCompleted() {
if (request_status_ != net::OK)
data_received_.clear();
if (!read_completed_callback_.is_null())
read_completed_callback_.Run(data_received_, request_status_);
}
ReadCompletedCallback read_completed_callback_;
scoped_refptr<net::IOBuffer> buffer_;
std::string data_received_;
int request_status_;
DISALLOW_COPY_AND_ASSIGN(TestURLRequestDelegate);
};
class TestURLRequestInterceptingJobFactory
: public net::URLRequestInterceptingJobFactory {
public:
TestURLRequestInterceptingJobFactory(
std::unique_ptr<net::URLRequestJobFactory> job_factory,
std::unique_ptr<net::URLRequestInterceptor> interceptor,
content::WebContents* web_contents)
: net::URLRequestInterceptingJobFactory(std::move(job_factory),
std::move(interceptor)),
web_contents_(web_contents) {}
~TestURLRequestInterceptingJobFactory() override {}
net::URLRequestJob* MaybeCreateJobWithProtocolHandler(
const std::string& scheme,
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
net::URLRequestJob* job = net::URLRequestInterceptingJobFactory::
MaybeCreateJobWithProtocolHandler(scheme, request, network_delegate);
if (job) {
static_cast<OfflinePageRequestJob*>(job)->SetDelegateForTesting(
std::make_unique<OfflinePageRequestJobTestDelegate>(web_contents_,
kTabId));
}
return job;
}
private:
content::WebContents* web_contents_;
DISALLOW_COPY_AND_ASSIGN(TestURLRequestInterceptingJobFactory);
};
class TestNetworkChangeNotifier : public net::NetworkChangeNotifier {
public:
TestNetworkChangeNotifier() : online_(true) {}
~TestNetworkChangeNotifier() override {}
net::NetworkChangeNotifier::ConnectionType GetCurrentConnectionType()
const override {
return online_ ? net::NetworkChangeNotifier::CONNECTION_UNKNOWN
: net::NetworkChangeNotifier::CONNECTION_NONE;
}
bool online() const { return online_; }
void set_online(bool online) { online_ = online; }
private:
bool online_;
DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifier);
};
class TestPreviewsDecider : public previews::PreviewsDecider {
public:
TestPreviewsDecider() : should_allow_preview_(false) {}
~TestPreviewsDecider() override {}
bool ShouldAllowPreview(const net::URLRequest& request,
previews::PreviewsType type) const override {
return should_allow_preview_;
}
bool ShouldAllowPreviewAtECT(
const net::URLRequest& request,
previews::PreviewsType type,
net::EffectiveConnectionType effective_connection_type_threshold,
const std::vector<std::string>& host_blacklist_from_server)
const override {
return should_allow_preview_;
}
bool IsURLAllowedForPreview(const net::URLRequest& request,
previews::PreviewsType type) const override {
return should_allow_preview_;
}
bool should_allow_preview() const { return should_allow_preview_; }
void set_should_allow_preview(bool should_allow_preview) {
should_allow_preview_ = should_allow_preview;
}
private:
bool should_allow_preview_;
DISALLOW_COPY_AND_ASSIGN(TestPreviewsDecider);
};
class TestOfflinePageArchiver : public OfflinePageArchiver {
public:
TestOfflinePageArchiver(const GURL& url,
const base::FilePath& archive_file_path,
int archive_file_size,
const std::string& digest)
: url_(url),
archive_file_path_(archive_file_path),
archive_file_size_(archive_file_size),
digest_(digest) {}
~TestOfflinePageArchiver() override {}
void CreateArchive(const base::FilePath& archives_dir,
const CreateArchiveParams& create_archive_params,
const CreateArchiveCallback& callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(callback, this, ArchiverResult::SUCCESSFULLY_CREATED, url_,
archive_file_path_, base::string16(), archive_file_size_,
digest_));
}
void PublishArchive(
const OfflinePageItem& offline_page,
const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
const base::FilePath& new_file_path,
SystemDownloadManager* download_manager,
PublishArchiveDoneCallback publish_done_callback) override {
publish_archive_result_.move_result = SavePageResult::SUCCESS;
publish_archive_result_.new_file_path = offline_page.file_path;
publish_archive_result_.download_id = 0;
std::move(publish_done_callback)
.Run(offline_page, &publish_archive_result_);
}
private:
const GURL url_;
const base::FilePath archive_file_path_;
const int archive_file_size_;
const std::string digest_;
PublishArchiveResult publish_archive_result_;
DISALLOW_COPY_AND_ASSIGN(TestOfflinePageArchiver);
};
// Helper function to make a character array filled with |size| bytes of
// test content.
std::string MakeContentOfSize(int size) {
EXPECT_GE(size, 0);
std::string result;
result.reserve(size);
for (int i = 0; i < size; i++)
result.append(1, static_cast<char>(i % 256));
return result;
}
} // namespace
class OfflinePageRequestJobTest : public testing::Test {
public:
OfflinePageRequestJobTest();
~OfflinePageRequestJobTest() override {}
void SetUp() override;
void TearDown() override;
void SimulateHasNetworkConnectivity(bool has_connectivity);
void RunUntilIdle();
void WaitForAsyncOperation();
base::FilePath CreateFileWithContent(const std::string& content);
// Returns an offline id of the saved page.
// |file_path| in SavePublicPage and SaveInternalPage can be either absolute
// or relative. If relative, |file_path| will be appended to public/internal
// archive directory used for the testing.
// |file_path| in SavePage should be absolute.
int64_t SavePublicPage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest);
int64_t SaveInternalPage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest);
int64_t SavePage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest);
OfflinePageItem GetPage(int64_t offline_id);
void LoadPage(const GURL& url);
void LoadPageWithHeaders(const GURL& url,
const net::HttpRequestHeaders& extra_headers);
void InterceptRequest(const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
content::ResourceType resource_type);
// Expect exactly one count of |result| UMA reported. No other bucket should
// have sample.
void ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result);
// Expect exactly |count| of |result| UMA reported. No other bucket should
// have sample.
void ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result,
int count);
// Expect one count of |result| UMA reported. Other buckets may have samples
// as well.
void ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result);
// Expect no samples to have been reported to the aggregated results
// histogram.
void ExpectNoSamplesInAggregatedRequestResult();
void ExpectOpenFileErrorCode(int result);
void ExpectSeekFileErrorCode(int result);
void ExpectAccessEntryPoint(
OfflinePageRequestJob::AccessEntryPoint entry_point);
void ExpectNoAccessEntryPoint();
void ExpectOfflinePageSizeUniqueSample(int bucket, int count);
void ExpectOfflinePageSizeTotalSuffixCount(int count);
void ExpectOnlinePageSizeUniqueSample(int bucket, int count);
void ExpectOnlinePageSizeTotalSuffixCount(int count);
void ExpectOfflinePageAccessCount(int64_t offline_id, int count);
void ExpectNoOfflinePageServed(
int64_t offline_id,
OfflinePageRequestJob::AggregatedRequestResult expected_request_result);
void ExpectOfflinePageServed(
int64_t expected_offline_id,
int expected_file_size,
OfflinePageRequestJob::AggregatedRequestResult expected_request_result);
// Use the offline header with specific reason and offline_id. Return the
// full header string.
std::string UseOfflinePageHeader(OfflinePageHeader::Reason reason,
int64_t offline_id);
std::string UseOfflinePageHeaderForIntent(OfflinePageHeader::Reason reason,
int64_t offline_id,
const GURL& intent_url);
net::TestURLRequestContext* url_request_context() {
return test_url_request_context_.get();
}
Profile* profile() { return profile_; }
OfflinePageTabHelper* offline_page_tab_helper() const {
return offline_page_tab_helper_;
}
int request_status() const { return request_status_; }
int bytes_read() const { return data_received_.length(); }
const std::string& data_received() const { return data_received_; }
bool is_offline_page_set_in_navigation_data() const {
return is_offline_page_set_in_navigation_data_;
}
TestPreviewsDecider* test_previews_decider() {
return test_previews_decider_.get();
}
bool is_connected_with_good_network() {
return network_change_notifier_->online() &&
// Exclude prohibitively slow network.
!test_previews_decider_->should_allow_preview() &&
// Exclude flaky network.
offline_page_header_.reason != OfflinePageHeader::Reason::NET_ERROR;
}
private:
static std::unique_ptr<KeyedService> BuildTestOfflinePageModel(
content::BrowserContext* context);
// TODO(https://crbug.com/809610): The static members below will be removed
// once the reference to BuildTestOfflinePageModel in SetUp is converted to a
// base::OnceCallback.
static base::FilePath private_archives_dir_;
static base::FilePath public_archives_dir_;
OfflinePageRequestJob::AccessEntryPoint GetExpectedAccessEntryPoint() const;
void OnSavePageDone(SavePageResult result, int64_t offline_id);
std::unique_ptr<net::URLRequest> CreateRequest(
const GURL& url,
const std::string& method,
content::ResourceType resource_type);
void OnGetPageByOfflineIdDone(const OfflinePageItem* pages);
void ReadCompleted(const std::string& data_received,
int request_status,
bool is_offline_page_set_in_navigation_data);
// Runs on IO thread.
void CreateFileWithContentOnIO(const std::string& content,
const base::Closure& callback);
void SetUpNetworkObjectsOnIO();
void TearDownNetworkObjectsOnIO();
void InterceptRequestOnIO(const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
content::ResourceType resource_type);
void ReadCompletedOnIO(const std::string& data_received, int request_status);
void TearDownOnReadCompletedOnIO(const std::string& data_received,
int request_status,
bool is_offline_page_set_in_navigation_data);
content::TestBrowserThreadBundle thread_bundle_;
TestingProfileManager profile_manager_;
TestingProfile* profile_;
std::unique_ptr<content::WebContents> web_contents_;
base::HistogramTester histogram_tester_;
OfflinePageTabHelper* offline_page_tab_helper_; // Not owned.
int64_t last_offline_id_;
std::string data_received_;
int request_status_;
bool is_offline_page_set_in_navigation_data_;
OfflinePageItem page_;
OfflinePageHeader offline_page_header_;
// These are not thread-safe. But they can be used in the pattern that
// setting the state is done first from one thread and reading this state
// can be from any other thread later.
std::unique_ptr<TestNetworkChangeNotifier> network_change_notifier_;
std::unique_ptr<TestPreviewsDecider> test_previews_decider_;
// These should only be accessed purely from IO thread.
std::unique_ptr<net::TestURLRequestContext> test_url_request_context_;
std::unique_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_;
std::unique_ptr<net::URLRequestInterceptingJobFactory>
intercepting_job_factory_;
std::unique_ptr<TestURLRequestDelegate> url_request_delegate_;
std::unique_ptr<net::URLRequest> request_;
base::ScopedTempDir private_archives_temp_base_dir_;
base::ScopedTempDir public_archives_temp_base_dir_;
base::ScopedTempDir temp_dir_;
base::FilePath temp_file_path_;
int file_name_sequence_num_ = 0;
bool async_operation_completed_ = false;
base::Closure async_operation_completed_callback_;
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestJobTest);
};
OfflinePageRequestJobTest::OfflinePageRequestJobTest()
: thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD),
profile_manager_(TestingBrowserProcess::GetGlobal()),
last_offline_id_(0),
request_status_(net::ERR_IO_PENDING),
is_offline_page_set_in_navigation_data_(false),
network_change_notifier_(new TestNetworkChangeNotifier),
test_previews_decider_(new TestPreviewsDecider) {}
void OfflinePageRequestJobTest::SetUp() {
// Create a test profile.
ASSERT_TRUE(profile_manager_.SetUp());
profile_ = profile_manager_.CreateTestingProfile("Profile 1");
// Create a test web contents.
web_contents_.reset(content::WebContents::Create(
content::WebContents::CreateParams(profile_)));
OfflinePageTabHelper::CreateForWebContents(web_contents_.get());
offline_page_tab_helper_ =
OfflinePageTabHelper::FromWebContents(web_contents_.get());
// Set up the factory for testing.
// Note: The extra dir into the temp folder is needed so that the helper
// dir-copy operation works properly. That operation copies the source dir
// final path segment into the destination, and not only its immediate
// contents so this same-named path here makes the archive dir variable point
// to the correct location.
// TODO(romax): add the more recent "temporary" dir here instead of reusing
// the private one.
ASSERT_TRUE(private_archives_temp_base_dir_.CreateUniqueTempDir());
private_archives_dir_ = private_archives_temp_base_dir_.GetPath().AppendASCII(
kPrivateOfflineFileDir);
ASSERT_TRUE(public_archives_temp_base_dir_.CreateUniqueTempDir());
public_archives_dir_ = public_archives_temp_base_dir_.GetPath().AppendASCII(
kPublicOfflineFileDir);
OfflinePageModelFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), &OfflinePageRequestJobTest::BuildTestOfflinePageModel);
// Initialize OfflinePageModel.
OfflinePageModelTaskified* model = static_cast<OfflinePageModelTaskified*>(
OfflinePageModelFactory::GetForBrowserContext(profile()));
// Skip the logic to clear the original URL if it is same as final URL.
// This is needed in order to test that offline page request handler can
// omit the redirect under this circumstance, for compatibility with the
// metadata already written to the store.
model->SetSkipClearingOriginalUrlForTesting();
// Move test data files into their respective temporary test directories. The
// model's maintenance tasks must not be executed in the meantime otherwise
// these files will be wiped by consistency checks.
base::FilePath test_data_dir_path;
PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_path);
base::FilePath test_data_private_archives_dir =
test_data_dir_path.AppendASCII(kPrivateOfflineFileDir);
ASSERT_TRUE(base::CopyDirectory(test_data_private_archives_dir,
private_archives_dir_.DirName(), true));
base::FilePath test_data_public_archives_dir =
test_data_dir_path.AppendASCII(kPublicOfflineFileDir);
ASSERT_TRUE(base::CopyDirectory(test_data_public_archives_dir,
public_archives_dir_.DirName(), true));
}
void OfflinePageRequestJobTest::TearDown() {
EXPECT_TRUE(private_archives_temp_base_dir_.Delete());
EXPECT_TRUE(public_archives_temp_base_dir_.Delete());
// This check confirms that the model's maintenance tasks were not executed
// during the test run.
histogram_tester_.ExpectTotalCount("OfflinePages.ClearTemporaryPages.Result",
0);
}
void OfflinePageRequestJobTest::SimulateHasNetworkConnectivity(bool online) {
network_change_notifier_->set_online(online);
}
void OfflinePageRequestJobTest::RunUntilIdle() {
base::RunLoop().RunUntilIdle();
}
void OfflinePageRequestJobTest::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();
}
void OfflinePageRequestJobTest::CreateFileWithContentOnIO(
const std::string& content,
const base::Closure& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!temp_dir_.IsValid()) {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
std::string file_name("test");
file_name += base::IntToString(file_name_sequence_num_++);
file_name += ".mht";
temp_file_path_ = temp_dir_.GetPath().AppendASCII(file_name);
ASSERT_TRUE(base::WriteFile(temp_file_path_, content.c_str(),
content.length()) != -1);
callback.Run();
}
base::FilePath OfflinePageRequestJobTest::CreateFileWithContent(
const std::string& content) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::RunLoop run_loop;
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::CreateFileWithContentOnIO,
base::Unretained(this), content, run_loop.QuitClosure()));
run_loop.Run();
return temp_file_path_;
}
void OfflinePageRequestJobTest::SetUpNetworkObjectsOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (test_url_request_context_.get())
return;
url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl);
// Create a context with delayed initialization.
test_url_request_context_.reset(new net::TestURLRequestContext(true));
// Install the interceptor.
std::unique_ptr<net::URLRequestInterceptor> interceptor(
new OfflinePageRequestInterceptor(test_previews_decider_.get()));
std::unique_ptr<net::URLRequestJobFactoryImpl> job_factory_impl(
new net::URLRequestJobFactoryImpl());
intercepting_job_factory_.reset(new TestURLRequestInterceptingJobFactory(
std::move(job_factory_impl), std::move(interceptor),
web_contents_.get()));
test_url_request_context_->set_job_factory(intercepting_job_factory_.get());
test_url_request_context_->Init();
}
void OfflinePageRequestJobTest::TearDownNetworkObjectsOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
request_.reset();
url_request_delegate_.reset();
intercepting_job_factory_.reset();
url_request_job_factory_.reset();
test_url_request_context_.reset();
}
std::unique_ptr<net::URLRequest> OfflinePageRequestJobTest::CreateRequest(
const GURL& url,
const std::string& method,
content::ResourceType resource_type) {
url_request_delegate_ = std::make_unique<TestURLRequestDelegate>(base::Bind(
&OfflinePageRequestJobTest::ReadCompletedOnIO, base::Unretained(this)));
std::unique_ptr<net::URLRequest> request =
url_request_context()->CreateRequest(url, net::DEFAULT_PRIORITY,
url_request_delegate_.get());
request->set_method(method);
content::ResourceRequestInfo::AllocateForTesting(
request.get(), resource_type, nullptr, /*render_process_id=*/1,
/*render_view_id=*/-1,
/*render_frame_id=*/1,
/*is_main_frame=*/true,
/*allow_download=*/true,
/*is_async=*/true, content::PREVIEWS_OFF,
std::make_unique<ChromeNavigationUIData>());
return request;
}
void OfflinePageRequestJobTest::ExpectOneUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result) {
histogram_tester_.ExpectUniqueSample(kAggregatedRequestResultHistogram,
static_cast<int>(result), 1);
}
void OfflinePageRequestJobTest::
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result,
int count) {
histogram_tester_.ExpectUniqueSample(kAggregatedRequestResultHistogram,
static_cast<int>(result), count);
}
void OfflinePageRequestJobTest::
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult result) {
histogram_tester_.ExpectBucketCount(kAggregatedRequestResultHistogram,
static_cast<int>(result), 1);
}
void OfflinePageRequestJobTest::ExpectNoSamplesInAggregatedRequestResult() {
histogram_tester_.ExpectTotalCount(kAggregatedRequestResultHistogram, 0);
}
void OfflinePageRequestJobTest::ExpectOpenFileErrorCode(int result) {
histogram_tester_.ExpectUniqueSample(kOpenFileErrorCodeHistogram, -result, 1);
}
void OfflinePageRequestJobTest::ExpectSeekFileErrorCode(int result) {
histogram_tester_.ExpectUniqueSample(kSeekFileErrorCodeHistogram, -result, 1);
}
void OfflinePageRequestJobTest::ExpectAccessEntryPoint(
OfflinePageRequestJob::AccessEntryPoint entry_point) {
histogram_tester_.ExpectUniqueSample(
std::string(kAccessEntryPointHistogram) + kDownloadNamespace,
static_cast<int>(entry_point), 1);
}
void OfflinePageRequestJobTest::ExpectNoAccessEntryPoint() {
EXPECT_TRUE(
histogram_tester_.GetTotalCountsForPrefix(kAccessEntryPointHistogram)
.empty());
}
void OfflinePageRequestJobTest::ExpectOfflinePageSizeUniqueSample(
int bucket,
int count) {
histogram_tester_.ExpectUniqueSample(
std::string(kPageSizeAccessOfflineHistogramBase) + kDownloadNamespace,
bucket, count);
}
void OfflinePageRequestJobTest::ExpectOfflinePageSizeTotalSuffixCount(
int count) {
int total_offline_count = 0;
base::HistogramTester::CountsMap all_offline_counts =
histogram_tester_.GetTotalCountsForPrefix(
kPageSizeAccessOfflineHistogramBase);
for (const std::pair<std::string, base::HistogramBase::Count>&
namespace_and_count : all_offline_counts) {
total_offline_count += namespace_and_count.second;
}
EXPECT_EQ(count, total_offline_count)
<< "Wrong histogram samples count under prefix "
<< kPageSizeAccessOfflineHistogramBase << "*";
}
void OfflinePageRequestJobTest::ExpectOnlinePageSizeUniqueSample(
int bucket,
int count) {
histogram_tester_.ExpectUniqueSample(
std::string(kPageSizeAccessOnlineHistogramBase) + kDownloadNamespace,
bucket, count);
}
void OfflinePageRequestJobTest::ExpectOnlinePageSizeTotalSuffixCount(
int count) {
int online_count = 0;
base::HistogramTester::CountsMap all_online_counts =
histogram_tester_.GetTotalCountsForPrefix(
kPageSizeAccessOnlineHistogramBase);
for (const std::pair<std::string, base::HistogramBase::Count>&
namespace_and_count : all_online_counts) {
online_count += namespace_and_count.second;
}
EXPECT_EQ(count, online_count)
<< "Wrong histogram samples count under prefix "
<< kPageSizeAccessOnlineHistogramBase << "*";
}
void OfflinePageRequestJobTest::ExpectOfflinePageAccessCount(int64_t offline_id,
int count) {
OfflinePageItem offline_page = GetPage(offline_id);
EXPECT_EQ(count, offline_page.access_count);
}
void OfflinePageRequestJobTest::ExpectNoOfflinePageServed(
int64_t offline_id,
OfflinePageRequestJob::AggregatedRequestResult expected_request_result) {
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
if (expected_request_result !=
OfflinePageRequestJob::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX) {
ExpectOneUniqueSampleForAggregatedRequestResult(expected_request_result);
}
ExpectNoAccessEntryPoint();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
ExpectOfflinePageAccessCount(offline_id, 0);
}
void OfflinePageRequestJobTest::ExpectOfflinePageServed(
int64_t expected_offline_id,
int expected_file_size,
OfflinePageRequestJob::AggregatedRequestResult expected_request_result) {
EXPECT_EQ(net::OK, request_status_);
EXPECT_EQ(expected_file_size, bytes_read());
EXPECT_TRUE(is_offline_page_set_in_navigation_data());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(expected_offline_id,
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
if (expected_request_result !=
OfflinePageRequestJob::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX) {
ExpectOneUniqueSampleForAggregatedRequestResult(expected_request_result);
}
OfflinePageRequestJob::AccessEntryPoint expected_entry_point =
GetExpectedAccessEntryPoint();
ExpectAccessEntryPoint(expected_entry_point);
if (is_connected_with_good_network()) {
ExpectOnlinePageSizeUniqueSample(expected_file_size / 1024, 1);
ExpectOfflinePageSizeTotalSuffixCount(0);
} else {
ExpectOfflinePageSizeUniqueSample(expected_file_size / 1024, 1);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
ExpectOfflinePageAccessCount(expected_offline_id, 1);
}
OfflinePageRequestJob::AccessEntryPoint
OfflinePageRequestJobTest::GetExpectedAccessEntryPoint() const {
switch (offline_page_header_.reason) {
case OfflinePageHeader::Reason::DOWNLOAD:
return OfflinePageRequestJob::AccessEntryPoint::DOWNLOADS;
case OfflinePageHeader::Reason::NOTIFICATION:
return OfflinePageRequestJob::AccessEntryPoint::NOTIFICATION;
case OfflinePageHeader::Reason::FILE_URL_INTENT:
return OfflinePageRequestJob::AccessEntryPoint::FILE_URL_INTENT;
case OfflinePageHeader::Reason::CONTENT_URL_INTENT:
return OfflinePageRequestJob::AccessEntryPoint::CONTENT_URL_INTENT;
default:
return OfflinePageRequestJob::AccessEntryPoint::LINK;
}
}
std::string OfflinePageRequestJobTest::UseOfflinePageHeader(
OfflinePageHeader::Reason reason,
int64_t offline_id) {
DCHECK_NE(OfflinePageHeader::Reason::NONE, reason);
offline_page_header_.reason = reason;
if (offline_id)
offline_page_header_.id = base::Int64ToString(offline_id);
return offline_page_header_.GetCompleteHeaderString();
}
std::string OfflinePageRequestJobTest::UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason reason,
int64_t offline_id,
const GURL& intent_url) {
DCHECK_NE(OfflinePageHeader::Reason::NONE, reason);
DCHECK(offline_id);
offline_page_header_.reason = reason;
offline_page_header_.id = base::Int64ToString(offline_id);
offline_page_header_.intent_url = intent_url;
return offline_page_header_.GetCompleteHeaderString();
}
int64_t OfflinePageRequestJobTest::SavePublicPage(
const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest) {
base::FilePath final_path;
if (file_path.IsAbsolute()) {
final_path = file_path;
} else {
final_path = public_archives_dir_.Append(file_path);
}
return SavePage(url, original_url, final_path, file_size, digest);
}
int64_t OfflinePageRequestJobTest::SaveInternalPage(
const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest) {
base::FilePath final_path;
if (file_path.IsAbsolute()) {
final_path = file_path;
} else {
final_path = private_archives_dir_.Append(file_path);
}
return SavePage(url, original_url, final_path, file_size, digest);
}
int64_t OfflinePageRequestJobTest::SavePage(const GURL& url,
const GURL& original_url,
const base::FilePath& file_path,
int64_t file_size,
const std::string& digest) {
DCHECK(file_path.IsAbsolute());
static int item_counter = 0;
++item_counter;
std::unique_ptr<TestOfflinePageArchiver> archiver(
new TestOfflinePageArchiver(url, file_path, file_size, digest));
async_operation_completed_ = false;
OfflinePageModel::SavePageParams save_page_params;
save_page_params.url = url;
save_page_params.client_id =
ClientId(kDownloadNamespace, base::IntToString(item_counter));
save_page_params.original_url = original_url;
OfflinePageModelFactory::GetForBrowserContext(profile())->SavePage(
save_page_params, std::move(archiver),
base::Bind(&OfflinePageRequestJobTest::OnSavePageDone,
base::Unretained(this)));
WaitForAsyncOperation();
return last_offline_id_;
}
// static
std::unique_ptr<KeyedService>
OfflinePageRequestJobTest::BuildTestOfflinePageModel(
content::BrowserContext* context) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
base::ThreadTaskRunnerHandle::Get();
base::FilePath store_path =
context->GetPath().Append(chrome::kOfflinePageMetadataDirname);
std::unique_ptr<OfflinePageMetadataStoreSQL> metadata_store(
new OfflinePageMetadataStoreSQL(task_runner, store_path));
std::unique_ptr<SystemDownloadManager> download_manager(
new SystemDownloadManagerStub(kDownloadId, true));
// Since we're not saving page into temporary dir, it's set the same as the
// private dir.
std::unique_ptr<ArchiveManager> archive_manager(
new ArchiveManager(private_archives_dir_, private_archives_dir_,
public_archives_dir_, task_runner));
std::unique_ptr<base::Clock> clock(new base::DefaultClock);
return std::unique_ptr<KeyedService>(new OfflinePageModelTaskified(
std::move(metadata_store), std::move(archive_manager),
std::move(download_manager), task_runner, std::move(clock)));
}
// static
base::FilePath OfflinePageRequestJobTest::private_archives_dir_;
base::FilePath OfflinePageRequestJobTest::public_archives_dir_;
void OfflinePageRequestJobTest::OnSavePageDone(SavePageResult result,
int64_t offline_id) {
ASSERT_EQ(SavePageResult::SUCCESS, result);
last_offline_id_ = offline_id;
async_operation_completed_ = true;
if (!async_operation_completed_callback_.is_null())
async_operation_completed_callback_.Run();
}
OfflinePageItem OfflinePageRequestJobTest::GetPage(int64_t offline_id) {
OfflinePageModelFactory::GetForBrowserContext(profile())->GetPageByOfflineId(
offline_id,
base::Bind(&OfflinePageRequestJobTest::OnGetPageByOfflineIdDone,
base::Unretained(this)));
RunUntilIdle();
return page_;
}
void OfflinePageRequestJobTest::OnGetPageByOfflineIdDone(
const OfflinePageItem* page) {
ASSERT_TRUE(page);
page_ = *page;
}
void OfflinePageRequestJobTest::InterceptRequestOnIO(
const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
content::ResourceType resource_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
SetUpNetworkObjectsOnIO();
request_ = CreateRequest(url, method, resource_type);
if (!extra_headers.IsEmpty())
request_->SetExtraRequestHeaders(extra_headers);
request_->Start();
}
void OfflinePageRequestJobTest::InterceptRequest(
const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& extra_headers,
content::ResourceType resource_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::InterceptRequestOnIO,
base::Unretained(this), url, method, extra_headers,
resource_type));
base::RunLoop().Run();
}
void OfflinePageRequestJobTest::LoadPage(const GURL& url) {
InterceptRequest(url, "GET", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_MAIN_FRAME);
}
void OfflinePageRequestJobTest::LoadPageWithHeaders(
const GURL& url,
const net::HttpRequestHeaders& extra_headers) {
InterceptRequest(url, "GET", extra_headers,
content::RESOURCE_TYPE_MAIN_FRAME);
}
void OfflinePageRequestJobTest::ReadCompletedOnIO(
const std::string& data_received,
int request_status) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
bool is_offline_page_set_in_navigation_data = false;
const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request_.get());
ChromeNavigationUIData* navigation_data =
static_cast<ChromeNavigationUIData*>(info->GetNavigationUIData());
if (navigation_data) {
offline_pages::OfflinePageNavigationUIData* offline_page_data =
navigation_data->GetOfflinePageNavigationUIData();
if (offline_page_data && offline_page_data->is_offline_page())
is_offline_page_set_in_navigation_data = true;
}
// Since the caller is still holding a request object which we want to dispose
// as part of tearing down on IO thread, we need to do it in a separate task.
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::TearDownOnReadCompletedOnIO,
base::Unretained(this), data_received, request_status,
is_offline_page_set_in_navigation_data));
}
void OfflinePageRequestJobTest::TearDownOnReadCompletedOnIO(
const std::string& data_received,
int request_status,
bool is_offline_page_set_in_navigation_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
TearDownNetworkObjectsOnIO();
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&OfflinePageRequestJobTest::ReadCompleted,
base::Unretained(this), data_received, request_status,
is_offline_page_set_in_navigation_data));
}
void OfflinePageRequestJobTest::ReadCompleted(
const std::string& data_received,
int request_status,
bool is_offline_page_set_in_navigation_data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
data_received_ = data_received;
request_status_ = request_status;
is_offline_page_set_in_navigation_data_ =
is_offline_page_set_in_navigation_data;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
}
TEST_F(OfflinePageRequestJobTest, FailedToCreateRequestJob) {
SimulateHasNetworkConnectivity(false);
// Must be http/https URL.
InterceptRequest(GURL("ftp://host/doc"), "GET", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_MAIN_FRAME);
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
InterceptRequest(GURL("file:///path/doc"), "GET", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_MAIN_FRAME);
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
// Must be GET method.
InterceptRequest(kUrl, "POST", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_MAIN_FRAME);
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
InterceptRequest(kUrl, "HEAD", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_MAIN_FRAME);
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
// Must be main resource.
InterceptRequest(kUrl, "POST", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_SUB_FRAME);
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
InterceptRequest(kUrl, "POST", net::HttpRequestHeaders(),
content::RESOURCE_TYPE_IMAGE);
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
ExpectNoSamplesInAggregatedRequestResult();
ExpectOfflinePageSizeTotalSuffixCount(0);
ExpectOnlinePageSizeTotalSuffixCount(0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
LoadPage(kUrl2);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestJobTest,
DontLoadReloadOfflinePageOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// Treat this as a reloaded page.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::RELOAD, 0));
LoadPageWithHeaders(kUrl, extra_headers);
// The existentce of RELOAD header will force to treat the network as
// connected regardless current network condition. So we will fall back to
// the default handling immediately and no request result should be reported.
// Passing AGGREGATED_REQUEST_RESULT_MAX to skip checking request result in
// the helper function.
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
LoadPage(kUrl2);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectOfflinePageServed(offline_id, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
LoadPageWithHeaders(kUrl2, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, ForceLoadOfflinePageOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectOfflinePageServed(offline_id, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, PageNotFoundOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// Save an offline page.
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
LoadPageWithHeaders(kUrl2, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, DoNotLoadOfflinePageOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
LoadPage(kUrl);
// When the network is good, we will fall back to the default handling
// immediately. So no request result should be reported. Passing
// AGGREGATED_REQUEST_RESULT_MAX to skip checking request result in
// the helper function.
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX);
}
TEST_F(OfflinePageRequestJobTest, LoadMostRecentlyCreatedOfflinePage) {
SimulateHasNetworkConnectivity(false);
// Save 2 offline pages associated with same online URL, but pointing to
// different archive file.
int64_t offline_id1 =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
int64_t offline_id2 =
SaveInternalPage(kUrl, GURL(), kFilename2, kFileSize2, std::string());
// Load an URL that matches multiple offline pages. Expect that the most
// recently created offline page is fetched.
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id2, kFileSize2,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectOfflinePageAccessCount(offline_id1, 0);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageByOfflineID) {
SimulateHasNetworkConnectivity(true);
// Save 2 offline pages associated with same online URL, but pointing to
// different archive file.
int64_t offline_id1 =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
int64_t offline_id2 =
SaveInternalPage(kUrl, GURL(), kFilename2, kFileSize2, std::string());
// Load an URL with a specific offline ID designated in the custom header.
// Expect the offline page matching the offline id is fetched.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, offline_id1));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectOfflinePageServed(offline_id1, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
ExpectOfflinePageAccessCount(offline_id2, 0);
}
TEST_F(OfflinePageRequestJobTest, FailToLoadByOfflineIDOnUrlMismatch) {
SimulateHasNetworkConnectivity(true);
int64_t offline_id =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// The offline page found with specific offline ID does not match the passed
// online URL. Should fall back to find the offline page based on the online
// URL.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, offline_id));
LoadPageWithHeaders(kUrl2, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageForUrlWithFragment) {
SimulateHasNetworkConnectivity(false);
// Save an offline page associated with online URL without fragment.
int64_t offline_id1 =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
// Save another offline page associated with online URL that has a fragment.
GURL url2_with_fragment(kUrl2.spec() + "#ref");
int64_t offline_id2 = SaveInternalPage(url2_with_fragment, GURL(), kFilename2,
kFileSize2, std::string());
ExpectOfflinePageAccessCount(offline_id1, 0);
ExpectOfflinePageAccessCount(offline_id2, 0);
// Loads an url with fragment, that will match the offline URL without the
// fragment.
GURL url_with_fragment(kUrl.spec() + "#ref");
LoadPage(url_with_fragment);
ExpectOfflinePageServed(offline_id1, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectOfflinePageAccessCount(offline_id2, 0);
// Loads an url without fragment, that will match the offline URL with the
// fragment.
LoadPage(kUrl2);
EXPECT_EQ(kFileSize2, bytes_read());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_id2,
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK, 2);
ExpectOfflinePageSizeTotalSuffixCount(2);
ExpectOnlinePageSizeTotalSuffixCount(0);
ExpectOfflinePageAccessCount(offline_id1, 1);
ExpectOfflinePageAccessCount(offline_id2, 1);
// Loads an url with fragment, that will match the offline URL with different
// fragment.
GURL url2_with_different_fragment(kUrl2.spec() + "#different_ref");
LoadPage(url2_with_different_fragment);
EXPECT_EQ(kFileSize2, bytes_read());
ASSERT_TRUE(offline_page_tab_helper()->GetOfflinePageForTest());
EXPECT_EQ(offline_id2,
offline_page_tab_helper()->GetOfflinePageForTest()->offline_id);
ExpectMultiUniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK, 3);
ExpectOfflinePageSizeTotalSuffixCount(3);
ExpectOnlinePageSizeTotalSuffixCount(0);
ExpectOfflinePageAccessCount(offline_id1, 1);
ExpectOfflinePageAccessCount(offline_id2, 2);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageAfterRedirect) {
SimulateHasNetworkConnectivity(false);
// Save an offline page with same original URL and final URL.
int64_t offline_id =
SaveInternalPage(kUrl, kUrl2, kFilename1, kFileSize1, std::string());
// This should trigger redirect first.
LoadPage(kUrl2);
// Passing AGGREGATED_REQUEST_RESULT_MAX to skip checking request result in
// the helper function. Different checks will be done after that.
ExpectOfflinePageServed(offline_id, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
AGGREGATED_REQUEST_RESULT_MAX);
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
REDIRECTED_ON_DISCONNECTED_NETWORK);
ExpectOneNonuniqueSampleForAggregatedRequestResult(
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, NoRedirectForOfflinePageWithSameOriginalURL) {
SimulateHasNetworkConnectivity(false);
// Skip the logic to clear the original URL if it is same as final URL.
// This is needed in order to test that offline page request handler can
// omit the redirect under this circumstance, for compatibility with the
// metadata already written to the store.
OfflinePageModelTaskified* model = static_cast<OfflinePageModelTaskified*>(
OfflinePageModelFactory::GetForBrowserContext(profile()));
model->SetSkipClearingOriginalUrlForTesting();
// Save an offline page with same original URL and final URL.
int64_t offline_id =
SaveInternalPage(kUrl, kUrl, kFilename1, kFileSize1, std::string());
// Check if the original URL is still present.
OfflinePageItem page = GetPage(offline_id);
EXPECT_EQ(kUrl, page.original_url);
// No redirect should be triggered when original URL is same as final URL.
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageFromNonExistentInternalFile) {
SimulateHasNetworkConnectivity(false);
// Save an offline page pointing to non-existent internal archive file.
int64_t offline_id = SaveInternalPage(kUrl, GURL(), kNonexistentFilename,
kFileSize1, std::string());
LoadPage(kUrl);
ExpectNoOfflinePageServed(
offline_id,
OfflinePageRequestJob::AggregatedRequestResult::FILE_NOT_FOUND);
}
TEST_F(OfflinePageRequestJobTest, LoadOfflinePageFromNonExistentPublicFile) {
SimulateHasNetworkConnectivity(false);
// Save an offline page pointing to non-existent public archive file.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kNonexistentFilename, kFileSize1, kDigest1);
LoadPage(kUrl);
ExpectNoOfflinePageServed(
offline_id,
OfflinePageRequestJob::AggregatedRequestResult::FILE_NOT_FOUND);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
// Save an offline page in public location with mismatched file size.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kMismatchedFileSize, kDigest1);
LoadPage(kUrl);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
// Save an offline page in public location with mismatched file size.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kMismatchedFileSize, kDigest1);
LoadPage(kUrl);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched file size.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kMismatchedFileSize, kDigest1);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, FileSizeMismatchOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched file size.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kMismatchedFileSize, kDigest1);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnDisconnectedNetwork) {
SimulateHasNetworkConnectivity(false);
// Save an offline page in public location with mismatched digest.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, kMismatchedDigest);
LoadPage(kUrl);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnProhibitivelySlowNetwork) {
SimulateHasNetworkConnectivity(true);
test_previews_decider()->set_should_allow_preview(true);
// Save an offline page in public location with mismatched digest.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, kMismatchedDigest);
LoadPage(kUrl);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnConnectedNetwork) {
SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched digest.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, kMismatchedDigest);
// When custom offline header exists and contains value other than
// "reason=error", it means that offline page is forced to load.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, 0));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, DigestMismatchOnFlakyNetwork) {
SimulateHasNetworkConnectivity(true);
// Save an offline page in public location with mismatched digest.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, kMismatchedDigest);
// When custom offline header exists and contains "reason=error", it means
// that net error is hit in last request due to flaky network.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::NET_ERROR, 0));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_FLAKY_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, FailOnNoDigestForPublicArchiveFile) {
SimulateHasNetworkConnectivity(false);
// Save an offline page in public location with no digest.
int64_t offline_id =
SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
LoadPage(kUrl);
ExpectNoOfflinePageServed(offline_id,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_DISCONNECTED_NETWORK);
}
TEST_F(OfflinePageRequestJobTest, FailToLoadByOfflineIDOnDigestMismatch) {
SimulateHasNetworkConnectivity(true);
// Save 2 offline pages associated with same online URL, one in internal
// location, while another in public location with mismatched digest.
int64_t offline_id1 =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
int64_t offline_id2 =
SavePublicPage(kUrl, GURL(), kFilename1, kFileSize1, kMismatchedDigest);
// The offline page found with specific offline ID does not pass the
// validation. Though there is another page with the same URL, it will not be
// fetched. Instead, fall back to load the online URL.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(
UseOfflinePageHeader(OfflinePageHeader::Reason::DOWNLOAD, offline_id2));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectNoOfflinePageServed(offline_id1,
OfflinePageRequestJob::AggregatedRequestResult::
DIGEST_MISMATCH_ON_CONNECTED_NETWORK);
ExpectOfflinePageAccessCount(offline_id2, 0);
}
TEST_F(OfflinePageRequestJobTest, LoadOtherPageOnDigestMismatch) {
SimulateHasNetworkConnectivity(false);
// Save 2 offline pages associated with same online URL, one in internal
// location, while another in public location with mismatched digest.
int64_t offline_id1 =
SaveInternalPage(kUrl, GURL(), kFilename1, kFileSize1, std::string());
int64_t offline_id2 =
SavePublicPage(kUrl, GURL(), kFilename2, kFileSize2, kMismatchedDigest);
ExpectOfflinePageAccessCount(offline_id1, 0);
ExpectOfflinePageAccessCount(offline_id2, 0);
// There're 2 offline pages matching kUrl. The most recently created one
// should fail on mistmatched digest. The second most recently created offline
// page should work.
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id1, kFileSize1,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
ExpectOfflinePageAccessCount(offline_id2, 0);
}
TEST_F(OfflinePageRequestJobTest, TinyFile) {
SimulateHasNetworkConnectivity(false);
std::string expected_data("hello world");
base::FilePath temp_file_path = CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
int64_t offline_id = SavePublicPage(kUrl, GURL(), temp_file_path,
expected_size, expected_digest);
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id, expected_size,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, data_received());
}
TEST_F(OfflinePageRequestJobTest, SmallFile) {
SimulateHasNetworkConnectivity(false);
std::string expected_data(MakeContentOfSize(2 * 1024));
base::FilePath temp_file_path = CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
int64_t offline_id = SavePublicPage(kUrl, GURL(), temp_file_path,
expected_size, expected_digest);
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id, expected_size,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, data_received());
}
TEST_F(OfflinePageRequestJobTest, BigFile) {
SimulateHasNetworkConnectivity(false);
std::string expected_data(MakeContentOfSize(3 * 1024 * 1024));
base::FilePath temp_file_path = CreateFileWithContent(expected_data);
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
int64_t offline_id = SavePublicPage(kUrl, GURL(), temp_file_path,
expected_size, expected_digest);
LoadPage(kUrl);
ExpectOfflinePageServed(offline_id, expected_size,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_DISCONNECTED_NETWORK);
EXPECT_EQ(expected_data, data_received());
}
TEST_F(OfflinePageRequestJobTest, LoadFromFileUrlIntent) {
SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with unmodified data. The path to this file will be feed
// into "intent_url" of extra headers.
base::FilePath unmodified_file_path = CreateFileWithContent(expected_data);
// Create a file with modified data. An offline page is created to associate
// with this file, but with size and digest matching the unmodified version.
std::string modified_data(expected_data);
modified_data[10] = '@';
base::FilePath modified_file_path = CreateFileWithContent(modified_data);
int64_t offline_id = SavePublicPage(kUrl, GURL(), modified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// unmodified file. Expect the file from the intent URL is fetched.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(unmodified_file_path)));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectOfflinePageServed(offline_id, expected_size,
OfflinePageRequestJob::AggregatedRequestResult::
SHOW_OFFLINE_ON_CONNECTED_NETWORK);
EXPECT_EQ(expected_data, data_received());
}
TEST_F(OfflinePageRequestJobTest, IntentFileNotFound) {
SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with unmodified data. An offline page is created to associate
// with this file.
base::FilePath unmodified_file_path = CreateFileWithContent(expected_data);
// Get a path pointing to non-existing file. This path will be feed into
// "intent_url" of extra headers.
base::FilePath nonexistent_file_path =
unmodified_file_path.DirName().AppendASCII("nonexistent");
int64_t offline_id = SavePublicPage(kUrl, GURL(), unmodified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// non-existent file. Expect the request fails.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(nonexistent_file_path)));
LoadPageWithHeaders(kUrl, extra_headers);
ExpectOpenFileErrorCode(net::ERR_FILE_NOT_FOUND);
EXPECT_EQ(net::ERR_FAILED, request_status());
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
}
TEST_F(OfflinePageRequestJobTest, IntentFileModifiedInTheMiddle) {
SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with modified data in the middle. An offline page is created
// to associate with this modified file, but with size and digest matching the
// unmodified version.
std::string modified_data(expected_data);
modified_data[10] = '@';
base::FilePath modified_file_path = CreateFileWithContent(modified_data);
int64_t offline_id = SavePublicPage(kUrl, GURL(), modified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// modified file. Expect the request fails.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(modified_file_path)));
LoadPageWithHeaders(kUrl, extra_headers);
EXPECT_EQ(net::ERR_FAILED, request_status());
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
}
TEST_F(OfflinePageRequestJobTest, IntentFileModifiedWithMoreDataAppended) {
SimulateHasNetworkConnectivity(true);
std::string expected_data(MakeContentOfSize(2 * 1024));
ArchiveValidator archive_validator;
archive_validator.Update(expected_data.c_str(), expected_data.length());
std::string expected_digest = archive_validator.Finish();
int expected_size = expected_data.length();
// Create a file with more data appended. An offline page is created to
// associate with this modified file, but with size and digest matching the
// unmodified version.
std::string modified_data(expected_data);
modified_data += "foo";
base::FilePath modified_file_path = CreateFileWithContent(modified_data);
int64_t offline_id = SavePublicPage(kUrl, GURL(), modified_file_path,
expected_size, expected_digest);
// Load an URL with custom header that contains "intent_url" pointing to
// modified file. Expect the request fails.
net::HttpRequestHeaders extra_headers;
extra_headers.AddHeaderFromString(UseOfflinePageHeaderForIntent(
OfflinePageHeader::Reason::FILE_URL_INTENT, offline_id,
net::FilePathToFileURL(modified_file_path)));
LoadPageWithHeaders(kUrl, extra_headers);
EXPECT_EQ(net::ERR_FAILED, request_status());
EXPECT_EQ(0, bytes_read());
EXPECT_FALSE(is_offline_page_set_in_navigation_data());
EXPECT_FALSE(offline_page_tab_helper()->GetOfflinePageForTest());
}
} // namespace offline_pages