Commit 627b1562 authored by Jian Li's avatar Jian Li Committed by Commit Bot

Open & hold file handle in OfflinePageRequestJob for validation & read

OfflinePageRequestJob does not derive from URLRequestFileJob any more.
Instead, the file reading logic in URLRequestFileJob is integrated into
OfflinePageRequestJob such that the file can be opened and held for both
validation and reading.

Bug: 758690
Change-Id: Id33533316b79a919a365991aa0173bc8c71b5e4f
Reviewed-on: https://chromium-review.googlesource.com/865757Reviewed-by: default avatarDmitry Titov <dimich@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Commit-Queue: Jian Li <jianli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#532055}
parent 046bf9ba
......@@ -6,26 +6,42 @@
#define CHROME_BROWSER_OFFLINE_PAGES_OFFLINE_PAGE_REQUEST_JOB_H_
#include <memory>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "components/offline_pages/core/archive_validator.h"
#include "components/offline_pages/core/offline_page_item.h"
#include "components/offline_pages/core/request_header/offline_page_header.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/common/resource_type.h"
#include "net/url_request/url_request_file_job.h"
#include "net/url_request/url_request_job.h"
namespace base {
class FilePath;
}
namespace net {
class FileStream;
class IOBuffer;
} // namespace net
namespace previews {
class PreviewsDecider;
}
namespace offline_pages {
// A request job that serves content from offline file.
class OfflinePageRequestJob : public net::URLRequestFileJob {
// A request job that serves content from a trusted offline file, located either
// in internal directory or in public directory with digest validated, when a
// http/https URL is being navigated on disconnected or poor network. If no
// trusted offline file can be found, fall back to the default network handling
// which will try to load the live version.
//
// The only header handled by this request job is:
// * "X-Chrome-offline" custom header.
class OfflinePageRequestJob : public net::URLRequestJob {
public:
// This enum is used for UMA reporting. It contains all possible outcomes of
// handling requests that might service offline page in different network
......@@ -58,6 +74,7 @@ class OfflinePageRequestJob : public net::URLRequestFileJob {
DIGEST_MISMATCH_ON_FLAKY_NETWORK,
DIGEST_MISMATCH_ON_PROHIBITIVELY_SLOW_NETWORK,
DIGEST_MISMATCH_ON_CONNECTED_NETWORK,
FILE_NOT_FOUND,
AGGREGATED_REQUEST_RESULT_MAX
};
......@@ -86,6 +103,32 @@ class OfflinePageRequestJob : public net::URLRequestFileJob {
COUNT
};
enum class NetworkState {
// No network connection.
DISCONNECTED_NETWORK,
// Prohibitively slow means that the NetworkQualityEstimator reported a
// connection slow enough to warrant showing an offline page if available.
// This requires offline previews to be enabled and the URL of the request
// to be allowed by previews.
PROHIBITIVELY_SLOW_NETWORK,
// Network error received due to bad network, i.e. connected to a hotspot or
// proxy that does not have a working network.
FLAKY_NETWORK,
// Network is in working condition.
CONNECTED_NETWORK,
// Force to load the offline page if it is available, though network is in
// working condition.
FORCE_OFFLINE_ON_CONNECTED_NETWORK
};
// Describes the info about an offline page candidate.
struct Candidate {
OfflinePageItem offline_page;
// Whether the archive file is in internal directory, for which it can be
// deemed trusted without validation.
bool archive_is_in_internal_dir;
};
// Delegate that allows tests to overwrite certain behaviors.
class Delegate {
public:
......@@ -99,6 +142,17 @@ class OfflinePageRequestJob : public net::URLRequestFileJob {
virtual TabIdGetter GetTabIdGetter() const = 0;
};
class ThreadSafeArchiveValidator final
: public ArchiveValidator,
public base::RefCountedThreadSafe<ThreadSafeArchiveValidator> {
public:
ThreadSafeArchiveValidator() = default;
private:
friend class base::RefCountedThreadSafe<ThreadSafeArchiveValidator>;
~ThreadSafeArchiveValidator() override = default;
};
// Reports the aggregated result combining both request result and network
// state.
static void ReportAggregatedRequestResult(AggregatedRequestResult result);
......@@ -116,26 +170,28 @@ class OfflinePageRequestJob : public net::URLRequestFileJob {
// net::URLRequestJob overrides:
void Start() override;
void Kill() override;
bool IsRedirectResponse(GURL* location, int* http_status_code) override;
int ReadRawData(net::IOBuffer* dest, int dest_size) override;
void GetResponseInfo(net::HttpResponseInfo* info) override;
void GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override;
bool CopyFragmentOnRedirect(const GURL& location) const override;
bool GetMimeType(std::string* mime_type) const override;
void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override;
// net::URLRequestFileJob overrides:
void OnOpenComplete(int result) override;
void OnSeekComplete(int64_t result) override;
void OnReadComplete(net::IOBuffer* buf, int result) override;
void OnOfflineFilePathAvailable(const std::string& name_space,
const base::FilePath& offline_file_path);
void OnOfflineRedirectAvailabe(const GURL& redirected_url);
// Called when offline pages matching the request URL are found. The list is
// sorted based on creation date in descending order.
void OnOfflinePagesAvailable(const std::vector<Candidate>& candidates);
void SetDelegateForTesting(std::unique_ptr<Delegate> delegate);
private:
// net::URLRequestFileJob overrides:
bool CanAccessFile(const base::FilePath& original_path,
const base::FilePath& absolute_path) override;
enum class FileValidationResult {
// The file passes the digest validation and thus can be trusted.
FILE_VALIDATION_SUCCEEDED,
// The file does not exist.
FILE_NOT_FOUND,
// The digest validation fails.
FILE_VALIDATION_FAILED,
};
OfflinePageRequestJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate,
......@@ -148,8 +204,34 @@ class OfflinePageRequestJob : public net::URLRequestFileJob {
AccessEntryPoint GetAccessEntryPoint() const;
const OfflinePageItem& GetCurrentOfflinePage() const;
void OnTrustedOfflinePageFound();
void VisitTrustedOfflinePage();
void Redirect(const GURL& redirected_url);
void OpenFile(const net::CompletionCallback& callback);
// All the work related to validations.
void ValidateFile();
void GetFileSizeForValidation();
void DidGetFileSizeForValidation(const int64_t* actual_file_size);
void DidOpenForValidation(int result);
void ReadForValidation();
void DidReadForValidation(int result);
void DidComputeActualDigest(const std::string& actual_digest);
void OnFileValidationDone(FileValidationResult result);
// All the work related to serving from the archive file.
void DidOpenForServing(int result);
void DidSeekForServing(int64_t result);
void DidReadForServing(scoped_refptr<net::IOBuffer> buf, int result);
std::unique_ptr<Delegate> delegate_;
OfflinePageHeader offline_header_;
NetworkState network_state_;
// For redirect simulation.
scoped_refptr<net::HttpResponseHeaders> fake_headers_for_redirect_;
base::TimeTicks receive_redirect_headers_end_;
......@@ -158,6 +240,21 @@ class OfflinePageRequestJob : public net::URLRequestFileJob {
// Used to determine if an URLRequest is eligible for offline previews.
previews::PreviewsDecider* previews_decider_;
// To run any file related operations.
scoped_refptr<base::TaskRunner> file_task_runner_;
// For file validaton purpose.
std::vector<Candidate> candidates_;
size_t candidate_index_;
scoped_refptr<net::IOBuffer> buffer_;
scoped_refptr<ThreadSafeArchiveValidator> archive_validator_;
// For the purpose of serving from the archive file.
base::FilePath file_path_;
std::unique_ptr<net::FileStream> stream_;
int64_t remaining_bytes_;
bool has_range_header_;
base::WeakPtrFactory<OfflinePageRequestJob> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(OfflinePageRequestJob);
......
......@@ -14,14 +14,29 @@
namespace offline_pages {
ArchiveValidator::ArchiveValidator() {
secure_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
}
ArchiveValidator::~ArchiveValidator() = default;
void ArchiveValidator::Update(const char* input, size_t len) {
secure_hash_->Update(input, len);
}
std::string ArchiveValidator::Finish() {
std::string digest(crypto::kSHA256Length, 0);
secure_hash_->Finish(&(digest[0]), digest.size());
return digest;
}
// static
std::string ArchiveValidator::ComputeDigest(const base::FilePath& file_path) {
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid())
return std::string();
std::unique_ptr<crypto::SecureHash> secure_hash(
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
ArchiveValidator archive_validator;
const int kMaxBufferSize = 1024;
std::vector<char> buffer(kMaxBufferSize);
......@@ -29,14 +44,12 @@ std::string ArchiveValidator::ComputeDigest(const base::FilePath& file_path) {
do {
bytes_read = file.ReadAtCurrentPos(buffer.data(), kMaxBufferSize);
if (bytes_read > 0)
secure_hash->Update(buffer.data(), bytes_read);
archive_validator.Update(buffer.data(), bytes_read);
} while (bytes_read > 0);
if (bytes_read < 0)
return std::string();
std::string result_bytes(crypto::kSHA256Length, 0);
secure_hash->Finish(&result_bytes[0], result_bytes.size());
return result_bytes;
return archive_validator.Finish();
}
// static
......
......@@ -5,6 +5,7 @@
#ifndef COMPONENTS_OFFLINE_PAGES_CORE_ARCHIVE_VALIDATOR_H_
#define COMPONENTS_OFFLINE_PAGES_CORE_ARCHIVE_VALIDATOR_H_
#include <memory>
#include <string>
#include "base/macros.h"
......@@ -13,11 +14,21 @@ namespace base {
class FilePath;
}
namespace crypto {
class SecureHash;
}
namespace offline_pages {
// Contains all helper functions to validate an archive file.
// Used to validate an archive file.
class ArchiveValidator {
public:
ArchiveValidator();
virtual ~ArchiveValidator();
void Update(const char* input, size_t len);
std::string Finish();
// Computes a SHA256 digest of the specified file. Empty string will be
// returned if the digest cannot be computed.
static std::string ComputeDigest(const base::FilePath& file_path);
......@@ -29,7 +40,9 @@ class ArchiveValidator {
const std::string& expected_digest);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ArchiveValidator);
std::unique_ptr<crypto::SecureHash> secure_hash_;
DISALLOW_COPY_AND_ASSIGN(ArchiveValidator);
};
} // namespace offline_pages
......
......@@ -31628,6 +31628,7 @@ Called by update_net_trust_anchors.py.-->
<int value="17" label="Digest mismatch on flaky network"/>
<int value="18" label="Digest mismatch on prohibitively slow network"/>
<int value="19" label="Digest mismatch on connected network"/>
<int value="20" label="File not found"/>
</enum>
<enum name="OfflinePagesBackgroundImmediateStartStatus">
......@@ -55931,6 +55931,13 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries.
</summary>
</histogram>
<histogram name="OfflinePages.RequestJob.RangeHeader" enum="BooleanExists">
<owner>jianli@chromium.org</owner>
<summary>
Track whether the range header is provided when an offline page is served.
</summary>
</histogram>
<histogram name="OfflinePages.RequestJob.ReadFileErrorCode"
enum="NetErrorCodes">
<owner>jianli@chromium.org</owner>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment