Index: components/subresource_filter/core/browser/ruleset_service.cc |
diff --git a/components/subresource_filter/core/browser/ruleset_service.cc b/components/subresource_filter/core/browser/ruleset_service.cc |
index 86e3e967ec79c207243f03e9b3792cf1b3a4bcea..851563755888da4e03ac9b9336bd8d139f6eebe2 100644 |
--- a/components/subresource_filter/core/browser/ruleset_service.cc |
+++ b/components/subresource_filter/core/browser/ruleset_service.cc |
@@ -7,6 +7,7 @@ |
#include <utility> |
#include "base/bind.h" |
+#include "base/callback.h" |
#include "base/files/file_proxy.h" |
#include "base/files/file_util.h" |
#include "base/files/scoped_temp_dir.h" |
@@ -22,6 +23,8 @@ |
#include "components/prefs/pref_service.h" |
#include "components/subresource_filter/core/browser/ruleset_distributor.h" |
#include "components/subresource_filter/core/browser/subresource_filter_constants.h" |
+#include "components/subresource_filter/core/common/indexed_ruleset.h" |
+#include "components/subresource_filter/core/common/proto/rules.pb.h" |
namespace subresource_filter { |
@@ -29,9 +32,6 @@ namespace subresource_filter { |
namespace { |
-// The current binary format version of the serialized ruleset. |
-const int kCurrentRulesetFormatVersion = 10; |
- |
// Names of the preferences storing the most recent ruleset version that |
// was successfully stored to disk. |
const char kSubresourceFilterRulesetContentVersion[] = |
@@ -39,64 +39,60 @@ const char kSubresourceFilterRulesetContentVersion[] = |
const char kSubresourceFilterRulesetFormatVersion[] = |
"subresource_filter.ruleset_version.format"; |
-base::FilePath GetSubdirectoryPathForVersion(const base::FilePath& base_dir, |
- const RulesetVersion& version) { |
- return base_dir.AppendASCII(version.AsString()); |
-} |
- |
base::FilePath GetRulesetDataFilePath(const base::FilePath& version_directory) { |
return version_directory.Append(kRulesetDataFileName); |
} |
} // namespace |
-// RulesetVersion ------------------------------------------------------------ |
+// IndexedRulesetVersion ------------------------------------------------------ |
-RulesetVersion::RulesetVersion() = default; |
-RulesetVersion::RulesetVersion(const std::string& content_version, |
- int format_version) |
+IndexedRulesetVersion::IndexedRulesetVersion() = default; |
+IndexedRulesetVersion::IndexedRulesetVersion(const std::string& content_version, |
+ int format_version) |
: content_version(content_version), format_version(format_version) {} |
-RulesetVersion::~RulesetVersion() = default; |
-RulesetVersion& RulesetVersion::operator=(const RulesetVersion&) = default; |
+IndexedRulesetVersion::~IndexedRulesetVersion() = default; |
+IndexedRulesetVersion& IndexedRulesetVersion::operator=( |
+ const IndexedRulesetVersion&) = default; |
// static |
-void RulesetVersion::RegisterPrefs(PrefRegistrySimple* registry) { |
+void IndexedRulesetVersion::RegisterPrefs(PrefRegistrySimple* registry) { |
registry->RegisterStringPref(kSubresourceFilterRulesetContentVersion, |
std::string()); |
registry->RegisterIntegerPref(kSubresourceFilterRulesetFormatVersion, 0); |
} |
// static |
-int RulesetVersion::CurrentFormatVersion() { |
- return kCurrentRulesetFormatVersion; |
+int IndexedRulesetVersion::CurrentFormatVersion() { |
+ return RulesetIndexer::kIndexedFormatVersion; |
} |
-void RulesetVersion::ReadFromPrefs(PrefService* local_state) { |
+void IndexedRulesetVersion::ReadFromPrefs(PrefService* local_state) { |
format_version = |
local_state->GetInteger(kSubresourceFilterRulesetFormatVersion); |
content_version = |
local_state->GetString(kSubresourceFilterRulesetContentVersion); |
} |
-bool RulesetVersion::IsValid() const { |
+bool IndexedRulesetVersion::IsValid() const { |
return format_version != 0 && !content_version.empty(); |
} |
-bool RulesetVersion::IsCurrentFormatVersion() const { |
+bool IndexedRulesetVersion::IsCurrentFormatVersion() const { |
return format_version == CurrentFormatVersion(); |
} |
-void RulesetVersion::SaveToPrefs(PrefService* local_state) const { |
- DCHECK(IsValid()); |
+void IndexedRulesetVersion::SaveToPrefs(PrefService* local_state) const { |
local_state->SetInteger(kSubresourceFilterRulesetFormatVersion, |
format_version); |
local_state->SetString(kSubresourceFilterRulesetContentVersion, |
content_version); |
} |
-std::string RulesetVersion::AsString() const { |
- DCHECK(IsValid()); |
- return base::IntToString(format_version) + "_" + content_version; |
+base::FilePath IndexedRulesetVersion::GetSubdirectoryPathForVersion( |
+ const base::FilePath& base_dir) const { |
+ return base_dir.AppendASCII(base::IntToString(format_version)) |
+ .AppendASCII(content_version); |
} |
// RulesetService ------------------------------------------------------------ |
@@ -104,32 +100,36 @@ std::string RulesetVersion::AsString() const { |
RulesetService::RulesetService( |
PrefService* local_state, |
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, |
- const base::FilePath& base_dir) |
+ const base::FilePath& indexed_ruleset_base_dir) |
: local_state_(local_state), |
blocking_task_runner_(blocking_task_runner), |
- base_dir_(base_dir) { |
+ indexed_ruleset_base_dir_(indexed_ruleset_base_dir) { |
DCHECK_NE(local_state_->GetInitializationStatus(), |
PrefService::INITIALIZATION_STATUS_WAITING); |
- RulesetVersion version_on_disk; |
- version_on_disk.ReadFromPrefs(local_state_); |
- if (version_on_disk.IsCurrentFormatVersion()) |
- OpenAndPublishRuleset(version_on_disk); |
+ IndexedRulesetVersion most_recently_indexed_version; |
+ most_recently_indexed_version.ReadFromPrefs(local_state_); |
+ if (most_recently_indexed_version.IsValid() && |
+ most_recently_indexed_version.IsCurrentFormatVersion()) |
+ OpenAndPublishRuleset(most_recently_indexed_version); |
} |
RulesetService::~RulesetService() {} |
-void RulesetService::StoreAndPublishUpdatedRuleset( |
- std::vector<uint8_t> ruleset_data, |
- const std::string& new_content_version) { |
- // TODO(engedy): The |format_version| corresponds to the output of the |
- // indexing step that will ultimately be performed here. |
- RulesetVersion new_version(new_content_version, |
- RulesetVersion::CurrentFormatVersion()); |
- base::PostTaskAndReplyWithResult( |
- blocking_task_runner_.get(), FROM_HERE, |
- base::Bind(&WriteRuleset, base_dir_, new_version, |
- std::move(ruleset_data)), |
- base::Bind(&RulesetService::OnWrittenRuleset, AsWeakPtr(), new_version)); |
+void RulesetService::IndexAndStoreAndPublishRulesetVersionIfNeeded( |
+ const base::FilePath& unindexed_ruleset_path, |
+ const std::string& content_version) { |
+ // Trying to store a ruleset with the same version for a second time would not |
+ // only be futile, but would fail on Windows due to "File System Tunneling" as |
+ // long as the previously stored copy of the rules is still in use. |
+ IndexedRulesetVersion most_recently_indexed_version; |
+ most_recently_indexed_version.ReadFromPrefs(local_state_); |
+ if (most_recently_indexed_version.IsCurrentFormatVersion() && |
+ most_recently_indexed_version.content_version == content_version) |
+ return; |
+ |
+ IndexAndStoreRuleset( |
+ unindexed_ruleset_path, content_version, |
+ base::Bind(&RulesetService::OpenAndPublishRuleset, AsWeakPtr())); |
} |
void RulesetService::RegisterDistributor( |
@@ -139,30 +139,83 @@ void RulesetService::RegisterDistributor( |
distributors_.push_back(std::move(distributor)); |
} |
-void RulesetService::NotifyRulesetVersionAvailable( |
- const std::string& rules, |
- const base::Version& version) {} |
+void RulesetService::IndexAndStoreRuleset( |
+ const base::FilePath& unindexed_ruleset_path, |
+ const std::string& content_version, |
+ const WriteRulesetCallback& success_callback) { |
+ IndexedRulesetVersion indexed_version; |
+ indexed_version.content_version = content_version; |
+ indexed_version.format_version = |
+ IndexedRulesetVersion::CurrentFormatVersion(); |
+ if (!indexed_version.IsValid()) |
+ return; |
+ base::PostTaskAndReplyWithResult( |
+ blocking_task_runner_.get(), FROM_HERE, |
+ base::Bind(&RulesetService::IndexAndWriteRuleset, |
+ indexed_ruleset_base_dir_, indexed_version, |
+ unindexed_ruleset_path), |
+ base::Bind(&RulesetService::OnWrittenRuleset, AsWeakPtr(), |
+ indexed_version, success_callback)); |
+} |
// static |
-bool RulesetService::WriteRuleset(const base::FilePath& base_dir, |
- const RulesetVersion& version, |
- std::vector<uint8_t> data) { |
+bool RulesetService::IndexAndWriteRuleset( |
+ const base::FilePath& indexed_ruleset_base_dir, |
+ const IndexedRulesetVersion& indexed_version, |
+ const base::FilePath& unindexed_ruleset_path) { |
base::ThreadRestrictions::AssertIOAllowed(); |
+ |
+ std::string unindexed_ruleset_data; |
+ if (!base::ReadFileToString(unindexed_ruleset_path, &unindexed_ruleset_data)) |
+ return false; |
+ |
+ // TODO(pkalinnikov): Implement streaming wire format here. |
+ proto::UrlRule rule; |
+ if (!rule.ParseFromString(unindexed_ruleset_data)) |
+ return false; |
+ |
+ RulesetIndexer indexer; |
+ indexer.AddUrlRule(rule); |
+ indexer.Finish(); |
+ |
+ return WriteRuleset(indexed_ruleset_base_dir, indexed_version, indexer.data(), |
+ indexer.size()); |
+} |
+ |
+// static |
+bool RulesetService::WriteRuleset( |
+ const base::FilePath& indexed_ruleset_base_dir, |
+ const IndexedRulesetVersion& indexed_version, |
+ const uint8_t* data, |
+ size_t length) { |
+ base::FilePath indexed_ruleset_version_dir = |
+ indexed_version.GetSubdirectoryPathForVersion(indexed_ruleset_base_dir); |
base::ScopedTempDir scratch_dir; |
- if (!scratch_dir.CreateUniqueTempDirUnderPath(base_dir)) |
+ if (!scratch_dir.CreateUniqueTempDirUnderPath( |
+ indexed_ruleset_version_dir.DirName())) { |
return false; |
+ } |
static_assert(sizeof(uint8_t) == sizeof(char), "Expected char = byte."); |
- const int data_size_in_chars = base::checked_cast<int>(data.size()); |
+ const int data_size_in_chars = base::checked_cast<int>(length); |
if (base::WriteFile(GetRulesetDataFilePath(scratch_dir.path()), |
- reinterpret_cast<const char*>(data.data()), |
+ reinterpret_cast<const char*>(data), |
data_size_in_chars) != data_size_in_chars) { |
return false; |
} |
- DCHECK(base::PathExists(base_dir)); |
- if (base::ReplaceFile(scratch_dir.path(), |
- GetSubdirectoryPathForVersion(base_dir, version), |
+ // Creating a temporary directory also makes sure the path (except for the |
+ // final segment) gets created. ReplaceFile will not create the path. |
+ DCHECK(base::PathExists(indexed_ruleset_version_dir.DirName())); |
+ |
+ // This will attempt to overwrite the previously stored ruleset with the same |
+ // version, if any. Doing so is needed in case the earlier write was |
+ // interrupted, but will fail on Windows in case the earlier write was |
+ // successful and the ruleset is in use. We should not normally get to here in |
+ // that case, however, due to the same-version check above. Even if we do, the |
+ // worst-case scenario is that a slightly-older ruleset version will be used |
+ // until next restart/ruleset update. |
+ if (base::ReplaceFile(scratch_dir.path(), indexed_ruleset_version_dir, |
nullptr)) { |
scratch_dir.Take(); |
return true; |
@@ -171,32 +224,48 @@ bool RulesetService::WriteRuleset(const base::FilePath& base_dir, |
return false; |
} |
-void RulesetService::OnWrittenRuleset(const RulesetVersion& version, |
- bool success) { |
+void RulesetService::OnWrittenRuleset( |
+ const IndexedRulesetVersion& version, |
+ const WriteRulesetCallback& result_callback, |
+ bool success) { |
+ DCHECK(!result_callback.is_null()); |
if (!success) |
return; |
- |
version.SaveToPrefs(local_state_); |
- OpenAndPublishRuleset(version); |
+ result_callback.Run(version); |
} |
-void RulesetService::OpenAndPublishRuleset(const RulesetVersion& version) { |
+void RulesetService::OpenAndPublishRuleset( |
+ const IndexedRulesetVersion& version) { |
ruleset_data_.reset(new base::FileProxy(blocking_task_runner_.get())); |
// On Windows, open the file with FLAG_SHARE_DELETE to allow deletion while |
// there are handles to it still open. Note that creating a new file at the |
- // same path would still be impossible until after the last of those handles |
- // is closed. |
+ // same path would still be impossible until after the last handle is closed. |
ruleset_data_->CreateOrOpen( |
- GetRulesetDataFilePath(GetSubdirectoryPathForVersion(base_dir_, version)), |
+ GetRulesetDataFilePath( |
+ version.GetSubdirectoryPathForVersion(indexed_ruleset_base_dir_)), |
base::File::FLAG_OPEN | base::File::FLAG_READ | |
base::File::FLAG_SHARE_DELETE, |
base::Bind(&RulesetService::OnOpenedRuleset, AsWeakPtr())); |
} |
void RulesetService::OnOpenedRuleset(base::File::Error error) { |
- // This should not happen unless |base_dir| has been tampered with. |
- if (!ruleset_data_ || !ruleset_data_->IsValid()) |
+ // The file has just been successfully written, so a failure here is unlikely |
+ // unless |indexed_ruleset_base_dir_| has been tampered with or there are disk |
+ // errors. Still, restore the invariant that a valid version in preferences |
+ // always points to an existing version of disk by invalidating the prefs. |
+ if (error != base::File::FILE_OK) { |
+ IndexedRulesetVersion().SaveToPrefs(local_state_); |
+ return; |
+ } |
+ |
+ // A second call to OpenAndPublishRuleset() may have reset |ruleset_data_| to |
+ // a new instance that is in the process of calling CreateOrOpen() on a newer |
+ // version. Bail out. |
+ DCHECK(ruleset_data_); |
+ if (!ruleset_data_->IsValid()) |
return; |
+ |
DCHECK_EQ(error, base::File::Error::FILE_OK); |
for (auto& distributor : distributors_) |
distributor->PublishNewVersion(ruleset_data_->DuplicateFile()); |