summaryrefslogtreecommitdiff
path: root/chromium/extensions/browser/content_verifier.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/extensions/browser/content_verifier.cc')
-rw-r--r--chromium/extensions/browser/content_verifier.cc290
1 files changed, 290 insertions, 0 deletions
diff --git a/chromium/extensions/browser/content_verifier.cc b/chromium/extensions/browser/content_verifier.cc
new file mode 100644
index 00000000000..ea464e81be8
--- /dev/null
+++ b/chromium/extensions/browser/content_verifier.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 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 "extensions/browser/content_verifier.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/browser/content_hash_fetcher.h"
+#include "extensions/browser/content_hash_reader.h"
+#include "extensions/browser/content_verifier_delegate.h"
+#include "extensions/browser/content_verifier_io_data.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension_l10n_util.h"
+
+namespace extensions {
+
+namespace {
+
+ContentVerifier::TestObserver* g_test_observer = NULL;
+
+// This function converts paths like "//foo/bar", "./foo/bar", and
+// "/foo/bar" to "foo/bar". It also converts path separators to "/".
+base::FilePath NormalizeRelativePath(const base::FilePath& path) {
+ if (path.ReferencesParent())
+ return base::FilePath();
+
+ std::vector<base::FilePath::StringType> parts;
+ path.GetComponents(&parts);
+ if (parts.empty())
+ return base::FilePath();
+
+ // Remove the first component if it is '.' or '/' or '//'.
+ const base::FilePath::StringType separators(
+ base::FilePath::kSeparators, base::FilePath::kSeparatorsLength);
+ if (!parts[0].empty() &&
+ (parts[0] == base::FilePath::kCurrentDirectory ||
+ parts[0].find_first_not_of(separators) == std::string::npos))
+ parts.erase(parts.begin());
+
+ // Note that elsewhere we always normalize path separators to '/' so this
+ // should work for all platforms.
+ return base::FilePath(
+ base::JoinString(parts, base::FilePath::StringType(1, '/')));
+}
+
+} // namespace
+
+// static
+void ContentVerifier::SetObserverForTests(TestObserver* observer) {
+ g_test_observer = observer;
+}
+
+ContentVerifier::ContentVerifier(content::BrowserContext* context,
+ ContentVerifierDelegate* delegate)
+ : shutdown_(false),
+ context_(context),
+ delegate_(delegate),
+ fetcher_(new ContentHashFetcher(
+ context,
+ delegate,
+ base::Bind(&ContentVerifier::OnFetchComplete, this))),
+ observer_(this),
+ io_data_(new ContentVerifierIOData) {
+}
+
+ContentVerifier::~ContentVerifier() {
+}
+
+void ContentVerifier::Start() {
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ observer_.Add(registry);
+}
+
+void ContentVerifier::Shutdown() {
+ shutdown_ = true;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ContentVerifierIOData::Clear, io_data_));
+ observer_.RemoveAll();
+ fetcher_.reset();
+}
+
+ContentVerifyJob* ContentVerifier::CreateJobFor(
+ const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const base::FilePath& relative_path) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ const ContentVerifierIOData::ExtensionData* data =
+ io_data_->GetData(extension_id);
+ if (!data)
+ return NULL;
+
+ base::FilePath normalized_path = NormalizeRelativePath(relative_path);
+
+ std::set<base::FilePath> paths;
+ paths.insert(normalized_path);
+ if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
+ return NULL;
+
+ // TODO(asargent) - we can probably get some good performance wins by having
+ // a cache of ContentHashReader's that we hold onto past the end of each job.
+ return new ContentVerifyJob(
+ new ContentHashReader(extension_id, data->version, extension_root,
+ normalized_path, delegate_->GetPublicKey()),
+ base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
+}
+
+void ContentVerifier::VerifyFailed(const std::string& extension_id,
+ ContentVerifyJob::FailureReason reason) {
+ if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
+ return;
+ }
+ if (shutdown_)
+ return;
+
+ VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ const Extension* extension =
+ registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+
+ if (!extension)
+ return;
+
+ if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
+ // If we failed because there were no hashes yet for this extension, just
+ // request some.
+ fetcher_->DoFetch(extension, true /* force */);
+ } else {
+ delegate_->VerifyFailed(extension_id, reason);
+ }
+}
+
+void ContentVerifier::OnExtensionLoaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension) {
+ if (shutdown_)
+ return;
+
+ ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
+ if (mode != ContentVerifierDelegate::NONE) {
+ // The browser image paths from the extension may not be relative (eg
+ // they might have leading '/' or './'), so we strip those to make
+ // comparing to actual relative paths work later on.
+ std::set<base::FilePath> original_image_paths =
+ delegate_->GetBrowserImagePaths(extension);
+
+ scoped_ptr<std::set<base::FilePath>> image_paths(
+ new std::set<base::FilePath>);
+ for (const auto& path : original_image_paths) {
+ image_paths->insert(NormalizeRelativePath(path));
+ }
+
+ scoped_ptr<ContentVerifierIOData::ExtensionData> data(
+ new ContentVerifierIOData::ExtensionData(
+ std::move(image_paths),
+ extension->version() ? *extension->version() : base::Version()));
+ content::BrowserThread::PostTask(content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ContentVerifierIOData::AddData,
+ io_data_,
+ extension->id(),
+ base::Passed(&data)));
+ fetcher_->ExtensionLoaded(extension);
+ }
+}
+
+void ContentVerifier::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ if (shutdown_)
+ return;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
+ if (fetcher_)
+ fetcher_->ExtensionUnloaded(extension);
+}
+
+void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
+ bool shouldVerifyAnyPathsResult) {
+ if (shouldVerifyAnyPathsResult)
+ delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
+}
+
+void ContentVerifier::OnFetchComplete(
+ const std::string& extension_id,
+ bool success,
+ bool was_force_check,
+ const std::set<base::FilePath>& hash_mismatch_paths) {
+ if (g_test_observer)
+ g_test_observer->OnFetchComplete(extension_id, success);
+
+ if (shutdown_)
+ return;
+
+ VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
+ const Extension* extension =
+ registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
+ if (!delegate_ || !extension)
+ return;
+
+ ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
+ if (was_force_check && !success &&
+ mode == ContentVerifierDelegate::ENFORCE_STRICT) {
+ // We weren't able to get verified_contents.json or weren't able to compute
+ // hashes.
+ delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
+ } else {
+ content::BrowserThread::PostTaskAndReplyWithResult(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
+ this,
+ extension_id,
+ extension->path(),
+ hash_mismatch_paths),
+ base::Bind(
+ &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
+ }
+}
+
+bool ContentVerifier::ShouldVerifyAnyPaths(
+ const std::string& extension_id,
+ const base::FilePath& extension_root,
+ const std::set<base::FilePath>& relative_paths) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ const ContentVerifierIOData::ExtensionData* data =
+ io_data_->GetData(extension_id);
+ if (!data)
+ return false;
+
+ const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
+
+ base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
+ scoped_ptr<std::set<std::string> > all_locales;
+
+ for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
+ i != relative_paths.end();
+ ++i) {
+ const base::FilePath& relative_path = *i;
+
+ if (relative_path == base::FilePath(kManifestFilename))
+ continue;
+
+ if (ContainsKey(browser_images, relative_path))
+ continue;
+
+ base::FilePath full_path = extension_root.Append(relative_path);
+ if (locales_dir.IsParent(full_path)) {
+ if (!all_locales) {
+ // TODO(asargent) - see if we can cache this list longer to avoid
+ // having to fetch it more than once for a given run of the
+ // browser. Maybe it can never change at runtime? (Or if it can, maybe
+ // there is an event we can listen for to know to drop our cache).
+ all_locales.reset(new std::set<std::string>);
+ extension_l10n_util::GetAllLocales(all_locales.get());
+ }
+
+ // Since message catalogs get transcoded during installation, we want
+ // to skip those paths.
+ if (full_path.DirName().DirName() == locales_dir &&
+ !extension_l10n_util::ShouldSkipValidation(
+ locales_dir, full_path.DirName(), *all_locales))
+ continue;
+ }
+ return true;
+ }
+ return false;
+}
+
+} // namespace extensions