blob: d460f14fde38d76aa574f2de76a64da164eaf3bf [file]
// 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.
#ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_RULESET_SERVICE_H_
#define COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_RULESET_SERVICE_H_
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/version.h"
class PrefService;
class PrefRegistrySimple;
namespace base {
class FileProxy;
class SequencedTaskRunner;
} // namespace base
namespace subresource_filter {
class RulesetIndexer;
class RulesetServiceDelegate;
// Encapsulates information about a version of unindexed subresource
// filtering rules on disk.
struct UnindexedRulesetInfo {
UnindexedRulesetInfo();
~UnindexedRulesetInfo();
// The version of the ruleset contents. Because the wire format of unindexed
// rules is expected to be stable over time (at least backwards compatible),
// the unindexed ruleset is uniquely identified by its content version.
//
// The version string must not be empty, but can be any string otherwise.
// There is no ordering defined on versions.
std::string content_version;
// The path to the file containing the unindexed subresource filtering rules.
base::FilePath ruleset_path;
// The (optional) path to a file containing the applicable license, which will
// be copied next to the indexed ruleset. For convenience, the lack of license
// can be indicated not only by setting |license_path| to empty, but also by
// setting it to any non existent path.
base::FilePath license_path;
};
// Encapsulates the combination of the binary format version of the indexed
// ruleset, and the version of the ruleset contents.
//
// In contrast to the unindexed ruleset, the binary format of the index data
// structures is expected to evolve over time, so the indexed ruleset is
// identified by a pair of versions: the content version of the rules that have
// been indexed; and the binary format version of the indexed data structures.
struct IndexedRulesetVersion {
IndexedRulesetVersion();
IndexedRulesetVersion(const std::string& content_version, int format_version);
~IndexedRulesetVersion();
IndexedRulesetVersion& operator=(const IndexedRulesetVersion&);
static void RegisterPrefs(PrefRegistrySimple* registry);
static int CurrentFormatVersion();
bool IsValid() const;
bool IsCurrentFormatVersion() const;
void SaveToPrefs(PrefService* local_state) const;
void ReadFromPrefs(PrefService* local_state);
std::string content_version;
int format_version = 0;
};
// Contains all utility functions that govern how files pertaining to indexed
// ruleset version should be organized on disk.
//
// The various indexed ruleset versions are kept in a two-level directory
// hierarchy based on their format and content version numbers, like so:
//
// |base_dir|
// |
// +--10 (format_version)
// | |
// | +--1 (content_version)
// | | \...
// | |
// | +--2 (content_version)
// | \...
// |
// +--11 (format_version)
// |
// +--2 (content_version)
// \...
//
class IndexedRulesetLocator {
public:
// Returns a path to a directory under |base_dir| where files corresponding to
// the given |version| should be stored.
static base::FilePath GetSubdirectoryPathForVersion(
const base::FilePath& base_dir,
const IndexedRulesetVersion& version);
static base::FilePath GetRulesetDataFilePath(
const base::FilePath& version_directory);
static base::FilePath GetLicenseFilePath(
const base::FilePath& version_directory);
static base::FilePath GetSentinelFilePath(
const base::FilePath& version_directory);
// Cleans up the |indexed_ruleset_base_dir| by deleting all obsoleted ruleset
// versions, keeping only:
// -- the |most_recent_version|, if it is valid,
// -- versions of the current format that have a sentinel file present.
// To be called on the |blocking_task_runner_|.
static void DeleteObsoleteRulesets(
const base::FilePath& indexed_ruleset_base_dir,
const IndexedRulesetVersion& most_recent_version);
};
// Responsible for indexing subresource filtering rules that are downloaded
// through the component updater; for versioned storage of the indexed ruleset;
// and for supplying the most up-to-date version of the indexed ruleset to the
// RulesetServiceDelegate, provided in the constructor, that abstracts away
// distribution of the ruleset to renderers.
//
// Files corresponding to each version of the indexed ruleset are stored in a
// separate subdirectory inside |indexed_ruleset_base_dir| named after the
// version. The version information of the most recent successfully stored
// ruleset is written into |local_state|. The invariant is maintained that the
// version pointed to by preferences, if valid, will exist on disk at any point
// in time. All file operations are posted to |blocking_task_runner|.
class RulesetService : public base::SupportsWeakPtr<RulesetService> {
public:
// Enumerates the possible outcomes of indexing a ruleset and writing it to
// disk. Used in UMA histograms, so the order of enumerators should not be
// changed.
enum class IndexAndWriteRulesetResult {
SUCCESS,
FAILED_CREATING_SCRATCH_DIR,
FAILED_WRITING_RULESET_DATA,
FAILED_WRITING_LICENSE,
FAILED_REPLACE_FILE,
FAILED_DELETE_PREEXISTING,
FAILED_OPENING_UNINDEXED_RULESET,
FAILED_PARSING_UNINDEXED_RULESET,
FAILED_CREATING_VERSION_DIR,
FAILED_CREATING_SENTINEL_FILE,
FAILED_DELETING_SENTINEL_FILE,
ABORTED_BECAUSE_SENTINEL_FILE_PRESENT,
// Insert new values before this line.
MAX,
};
// Creates a new instance that will immediately publish the most recently
// indexed version of the ruleset if one is available according to prefs.
// See class comments for details of arguments.
RulesetService(PrefService* local_state,
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
std::unique_ptr<RulesetServiceDelegate> delegate,
const base::FilePath& indexed_ruleset_base_dir);
virtual ~RulesetService();
// Indexes, stores, and publishes the given unindexed ruleset, unless its
// |content_version| matches that of the most recently indexed version, in
// which case it does nothing. The files comprising the unindexed ruleset
// need to remain accessible even after the method returns.
//
// Computation-heavy steps and I/O are performed on a background thread.
// Furthermore, to prevent start-up congestion, new rulesets provided via this
// method will not be processed until after start-up.
//
// Virtual so that it can be mocked out in tests.
virtual void IndexAndStoreAndPublishRulesetIfNeeded(
const UnindexedRulesetInfo& unindexed_ruleset_info);
// Exposed for browser tests.
RulesetServiceDelegate* delegate() { return delegate_.get(); }
private:
friend class SubresourceFilteringRulesetServiceTest;
FRIEND_TEST_ALL_PREFIXES(SubresourceFilteringRulesetServiceTest,
NewRuleset_WriteFailure);
FRIEND_TEST_ALL_PREFIXES(SubresourceFilteringRulesetServiceDeathTest,
NewRuleset_IndexingCrash);
using WriteRulesetCallback =
base::Callback<void(const IndexedRulesetVersion&)>;
// Reads the ruleset described in |unindexed_ruleset_info|, indexes it, and
// calls WriteRuleset() to persist the indexed ruleset. Returns the resulting
// indexed ruleset version, or an invalid version on error. To be called on
// the |blocking_task_runner_|.
static IndexedRulesetVersion IndexAndWriteRuleset(
const base::FilePath& indexed_ruleset_base_dir,
const UnindexedRulesetInfo& unindexed_ruleset_info);
// Reads the rules from the |unindexed_ruleset_file|, and indexes them using
// |indexer|. Returns whether the entire ruleset could be parsed.
static bool IndexRuleset(base::File unindexed_ruleset_file,
RulesetIndexer* indexer);
// Writes all files comprising the given |indexed_version| of the ruleset
// into the corresponding subdirectory in |indexed_ruleset_base_dir|.
// More specifically, it writes:
// -- the |indexed_ruleset_data| of the given |indexed_ruleset_size|,
// -- a copy of the LICENSE file at |license_path|, if exists.
// Returns true on success. To be called on the |blocking_task_runner_|.
// Attempts not to leave an incomplete copy in the target directory.
//
// Writing is factored out into this separate function so it can be
// independently exercised in tests.
static IndexAndWriteRulesetResult WriteRuleset(
const base::FilePath& indexed_ruleset_version_dir,
const base::FilePath& license_source_path,
const uint8_t* indexed_ruleset_data,
size_t indexed_ruleset_size);
// Indirections for accessing these routines, so as to allow overriding and
// injecting faults in tests.
static decltype(&IndexRuleset) g_index_ruleset_func;
static decltype(&base::ReplaceFile) g_replace_file_func;
// Performs indexing of the queued unindexed ruleset (if any) after start-up.
void InitializeAfterStartup();
// Posts a task to the |blocking_task_runner_| to index and persist the given
// unindexed ruleset. Then, on success, updates the most recently indexed
// version in preferences and invokes |success_callback| on the calling
// thread. There is no callback on failure.
void IndexAndStoreRuleset(const UnindexedRulesetInfo& unindexed_ruleset_info,
const WriteRulesetCallback& success_callback);
void OnWrittenRuleset(const WriteRulesetCallback& result_callback,
const IndexedRulesetVersion& version);
void OpenAndPublishRuleset(const IndexedRulesetVersion& version);
void OnOpenedRuleset(base::File::Error error);
PrefService* const local_state_;
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
std::unique_ptr<RulesetServiceDelegate> delegate_;
UnindexedRulesetInfo queued_unindexed_ruleset_info_;
bool is_after_startup_;
const base::FilePath indexed_ruleset_base_dir_;
std::unique_ptr<base::FileProxy> ruleset_data_;
DISALLOW_COPY_AND_ASSIGN(RulesetService);
};
} // namespace subresource_filter
#endif // COMPONENTS_SUBRESOURCE_FILTER_CORE_BROWSER_RULESET_SERVICE_H_