Roll src/third_party/WebKit a3b4a2e:7441784 (svn 202551:202552)
[chromium-blink-merge.git] / extensions / browser / content_verifier.cc
blob3c778929e031266086fd9e60eec329b9efd07b87
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/browser/content_verifier.h"
7 #include <algorithm>
9 #include "base/files/file_path.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_util.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "extensions/browser/content_hash_fetcher.h"
14 #include "extensions/browser/content_hash_reader.h"
15 #include "extensions/browser/content_verifier_delegate.h"
16 #include "extensions/browser/content_verifier_io_data.h"
17 #include "extensions/browser/extension_registry.h"
18 #include "extensions/common/constants.h"
19 #include "extensions/common/extension_l10n_util.h"
21 namespace extensions {
23 namespace {
25 ContentVerifier::TestObserver* g_test_observer = NULL;
27 // This function converts paths like "//foo/bar", "./foo/bar", and
28 // "/foo/bar" to "foo/bar". It also converts path separators to "/".
29 base::FilePath NormalizeRelativePath(const base::FilePath& path) {
30 if (path.ReferencesParent())
31 return base::FilePath();
33 std::vector<base::FilePath::StringType> parts;
34 path.GetComponents(&parts);
35 if (parts.empty())
36 return base::FilePath();
38 // Remove the first component if it is '.' or '/' or '//'.
39 const base::FilePath::StringType separators(
40 base::FilePath::kSeparators, base::FilePath::kSeparatorsLength);
41 if (!parts[0].empty() &&
42 (parts[0] == base::FilePath::kCurrentDirectory ||
43 parts[0].find_first_not_of(separators) == std::string::npos))
44 parts.erase(parts.begin());
46 // Note that elsewhere we always normalize path separators to '/' so this
47 // should work for all platforms.
48 return base::FilePath(
49 base::JoinString(parts, base::FilePath::StringType(1, '/')));
52 } // namespace
54 // static
55 void ContentVerifier::SetObserverForTests(TestObserver* observer) {
56 g_test_observer = observer;
59 ContentVerifier::ContentVerifier(content::BrowserContext* context,
60 ContentVerifierDelegate* delegate)
61 : shutdown_(false),
62 context_(context),
63 delegate_(delegate),
64 fetcher_(new ContentHashFetcher(
65 context,
66 delegate,
67 base::Bind(&ContentVerifier::OnFetchComplete, this))),
68 observer_(this),
69 io_data_(new ContentVerifierIOData) {
72 ContentVerifier::~ContentVerifier() {
75 void ContentVerifier::Start() {
76 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
77 observer_.Add(registry);
80 void ContentVerifier::Shutdown() {
81 shutdown_ = true;
82 content::BrowserThread::PostTask(
83 content::BrowserThread::IO,
84 FROM_HERE,
85 base::Bind(&ContentVerifierIOData::Clear, io_data_));
86 observer_.RemoveAll();
87 fetcher_.reset();
90 ContentVerifyJob* ContentVerifier::CreateJobFor(
91 const std::string& extension_id,
92 const base::FilePath& extension_root,
93 const base::FilePath& relative_path) {
94 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
96 const ContentVerifierIOData::ExtensionData* data =
97 io_data_->GetData(extension_id);
98 if (!data)
99 return NULL;
101 base::FilePath normalized_path = NormalizeRelativePath(relative_path);
103 std::set<base::FilePath> paths;
104 paths.insert(normalized_path);
105 if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
106 return NULL;
108 // TODO(asargent) - we can probably get some good performance wins by having
109 // a cache of ContentHashReader's that we hold onto past the end of each job.
110 return new ContentVerifyJob(
111 new ContentHashReader(extension_id, data->version, extension_root,
112 normalized_path, delegate_->GetPublicKey()),
113 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
116 void ContentVerifier::VerifyFailed(const std::string& extension_id,
117 ContentVerifyJob::FailureReason reason) {
118 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
119 content::BrowserThread::PostTask(
120 content::BrowserThread::UI,
121 FROM_HERE,
122 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
123 return;
125 if (shutdown_)
126 return;
128 VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
130 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
131 const Extension* extension =
132 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
134 if (!extension)
135 return;
137 if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
138 // If we failed because there were no hashes yet for this extension, just
139 // request some.
140 fetcher_->DoFetch(extension, true /* force */);
141 } else {
142 delegate_->VerifyFailed(extension_id, reason);
146 void ContentVerifier::OnExtensionLoaded(
147 content::BrowserContext* browser_context,
148 const Extension* extension) {
149 if (shutdown_)
150 return;
152 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
153 if (mode != ContentVerifierDelegate::NONE) {
154 // The browser image paths from the extension may not be relative (eg
155 // they might have leading '/' or './'), so we strip those to make
156 // comparing to actual relative paths work later on.
157 std::set<base::FilePath> original_image_paths =
158 delegate_->GetBrowserImagePaths(extension);
160 scoped_ptr<std::set<base::FilePath>> image_paths(
161 new std::set<base::FilePath>);
162 for (const auto& path : original_image_paths) {
163 image_paths->insert(NormalizeRelativePath(path));
166 scoped_ptr<ContentVerifierIOData::ExtensionData> data(
167 new ContentVerifierIOData::ExtensionData(
168 image_paths.Pass(),
169 extension->version() ? *extension->version() : base::Version()));
170 content::BrowserThread::PostTask(content::BrowserThread::IO,
171 FROM_HERE,
172 base::Bind(&ContentVerifierIOData::AddData,
173 io_data_,
174 extension->id(),
175 base::Passed(&data)));
176 fetcher_->ExtensionLoaded(extension);
180 void ContentVerifier::OnExtensionUnloaded(
181 content::BrowserContext* browser_context,
182 const Extension* extension,
183 UnloadedExtensionInfo::Reason reason) {
184 if (shutdown_)
185 return;
186 content::BrowserThread::PostTask(
187 content::BrowserThread::IO,
188 FROM_HERE,
189 base::Bind(
190 &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
191 if (fetcher_)
192 fetcher_->ExtensionUnloaded(extension);
195 void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
196 bool shouldVerifyAnyPathsResult) {
197 if (shouldVerifyAnyPathsResult)
198 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
201 void ContentVerifier::OnFetchComplete(
202 const std::string& extension_id,
203 bool success,
204 bool was_force_check,
205 const std::set<base::FilePath>& hash_mismatch_paths) {
206 if (g_test_observer)
207 g_test_observer->OnFetchComplete(extension_id, success);
209 if (shutdown_)
210 return;
212 VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
214 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
215 const Extension* extension =
216 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
217 if (!delegate_ || !extension)
218 return;
220 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
221 if (was_force_check && !success &&
222 mode == ContentVerifierDelegate::ENFORCE_STRICT) {
223 // We weren't able to get verified_contents.json or weren't able to compute
224 // hashes.
225 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
226 } else {
227 content::BrowserThread::PostTaskAndReplyWithResult(
228 content::BrowserThread::IO,
229 FROM_HERE,
230 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
231 this,
232 extension_id,
233 extension->path(),
234 hash_mismatch_paths),
235 base::Bind(
236 &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
240 bool ContentVerifier::ShouldVerifyAnyPaths(
241 const std::string& extension_id,
242 const base::FilePath& extension_root,
243 const std::set<base::FilePath>& relative_paths) {
244 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
245 const ContentVerifierIOData::ExtensionData* data =
246 io_data_->GetData(extension_id);
247 if (!data)
248 return false;
250 const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
252 base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
253 scoped_ptr<std::set<std::string> > all_locales;
255 for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
256 i != relative_paths.end();
257 ++i) {
258 const base::FilePath& relative_path = *i;
260 if (relative_path == base::FilePath(kManifestFilename))
261 continue;
263 if (ContainsKey(browser_images, relative_path))
264 continue;
266 base::FilePath full_path = extension_root.Append(relative_path);
267 if (locales_dir.IsParent(full_path)) {
268 if (!all_locales) {
269 // TODO(asargent) - see if we can cache this list longer to avoid
270 // having to fetch it more than once for a given run of the
271 // browser. Maybe it can never change at runtime? (Or if it can, maybe
272 // there is an event we can listen for to know to drop our cache).
273 all_locales.reset(new std::set<std::string>);
274 extension_l10n_util::GetAllLocales(all_locales.get());
277 // Since message catalogs get transcoded during installation, we want
278 // to skip those paths.
279 if (full_path.DirName().DirName() == locales_dir &&
280 !extension_l10n_util::ShouldSkipValidation(
281 locales_dir, full_path.DirName(), *all_locales))
282 continue;
284 return true;
286 return false;
289 } // namespace extensions