Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / extensions / browser / content_verifier.cc
blob8c05f1267f88771c23deaf0d1b6a0e8e2efbfcc4
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 ContentVerifier::ContentVerifier(content::BrowserContext* context,
24 ContentVerifierDelegate* delegate)
25 : shutdown_(false),
26 context_(context),
27 delegate_(delegate),
28 fetcher_(new ContentHashFetcher(
29 context,
30 delegate,
31 base::Bind(&ContentVerifier::OnFetchComplete, this))),
32 observer_(this),
33 io_data_(new ContentVerifierIOData) {
36 ContentVerifier::~ContentVerifier() {
39 void ContentVerifier::Start() {
40 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
41 observer_.Add(registry);
44 void ContentVerifier::Shutdown() {
45 shutdown_ = true;
46 content::BrowserThread::PostTask(
47 content::BrowserThread::IO,
48 FROM_HERE,
49 base::Bind(&ContentVerifierIOData::Clear, io_data_));
50 observer_.RemoveAll();
51 fetcher_.reset();
54 ContentVerifyJob* ContentVerifier::CreateJobFor(
55 const std::string& extension_id,
56 const base::FilePath& extension_root,
57 const base::FilePath& relative_path) {
58 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
60 const ContentVerifierIOData::ExtensionData* data =
61 io_data_->GetData(extension_id);
62 if (!data)
63 return NULL;
65 std::set<base::FilePath> paths;
66 paths.insert(relative_path);
67 if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
68 return NULL;
70 // TODO(asargent) - we can probably get some good performance wins by having
71 // a cache of ContentHashReader's that we hold onto past the end of each job.
72 return new ContentVerifyJob(
73 new ContentHashReader(extension_id,
74 data->version,
75 extension_root,
76 relative_path,
77 delegate_->PublicKey()),
78 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
81 void ContentVerifier::VerifyFailed(const std::string& extension_id,
82 ContentVerifyJob::FailureReason reason) {
83 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
84 content::BrowserThread::PostTask(
85 content::BrowserThread::UI,
86 FROM_HERE,
87 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
88 return;
90 if (shutdown_)
91 return;
93 VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
95 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
96 const Extension* extension =
97 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
99 if (!extension)
100 return;
102 if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
103 // If we failed because there were no hashes yet for this extension, just
104 // request some.
105 fetcher_->DoFetch(extension, true /* force */);
106 } else {
107 delegate_->VerifyFailed(extension_id, reason);
111 static base::FilePath MakeImagePathRelative(const base::FilePath& path) {
112 if (path.ReferencesParent())
113 return base::FilePath();
115 std::vector<base::FilePath::StringType> parts;
116 path.GetComponents(&parts);
117 if (parts.empty())
118 return base::FilePath();
120 // Remove the first component if it is '.' or '/' or '//'.
121 const base::FilePath::StringType separators(
122 base::FilePath::kSeparators, base::FilePath::kSeparatorsLength);
123 if (!parts[0].empty() &&
124 (parts[0] == base::FilePath::kCurrentDirectory ||
125 parts[0].find_first_not_of(separators) == std::string::npos))
126 parts.erase(parts.begin());
128 // Note that elsewhere we always normalize path separators to '/' so this
129 // should work for all platforms.
130 return base::FilePath(JoinString(parts, '/'));
133 void ContentVerifier::OnExtensionLoaded(
134 content::BrowserContext* browser_context,
135 const Extension* extension) {
136 if (shutdown_)
137 return;
139 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
140 if (mode != ContentVerifierDelegate::NONE) {
141 // The browser image paths from the extension may not be relative (eg
142 // they might have leading '/' or './'), so we strip those to make
143 // comparing to actual relative paths work later on.
144 std::set<base::FilePath> original_image_paths =
145 delegate_->GetBrowserImagePaths(extension);
147 scoped_ptr<std::set<base::FilePath>> image_paths(
148 new std::set<base::FilePath>);
149 for (const auto& path : original_image_paths) {
150 image_paths->insert(MakeImagePathRelative(path));
153 scoped_ptr<ContentVerifierIOData::ExtensionData> data(
154 new ContentVerifierIOData::ExtensionData(
155 image_paths.Pass(),
156 extension->version() ? *extension->version() : base::Version()));
157 content::BrowserThread::PostTask(content::BrowserThread::IO,
158 FROM_HERE,
159 base::Bind(&ContentVerifierIOData::AddData,
160 io_data_,
161 extension->id(),
162 base::Passed(&data)));
163 fetcher_->ExtensionLoaded(extension);
167 void ContentVerifier::OnExtensionUnloaded(
168 content::BrowserContext* browser_context,
169 const Extension* extension,
170 UnloadedExtensionInfo::Reason reason) {
171 if (shutdown_)
172 return;
173 content::BrowserThread::PostTask(
174 content::BrowserThread::IO,
175 FROM_HERE,
176 base::Bind(
177 &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
178 if (fetcher_)
179 fetcher_->ExtensionUnloaded(extension);
182 void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
183 bool shouldVerifyAnyPathsResult) {
184 if (shouldVerifyAnyPathsResult)
185 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
188 void ContentVerifier::OnFetchComplete(
189 const std::string& extension_id,
190 bool success,
191 bool was_force_check,
192 const std::set<base::FilePath>& hash_mismatch_paths) {
193 if (shutdown_)
194 return;
196 VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
198 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
199 const Extension* extension =
200 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
201 if (!delegate_ || !extension)
202 return;
204 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
205 if (was_force_check && !success &&
206 mode == ContentVerifierDelegate::ENFORCE_STRICT) {
207 // We weren't able to get verified_contents.json or weren't able to compute
208 // hashes.
209 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
210 } else {
211 content::BrowserThread::PostTaskAndReplyWithResult(
212 content::BrowserThread::IO,
213 FROM_HERE,
214 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
215 this,
216 extension_id,
217 extension->path(),
218 hash_mismatch_paths),
219 base::Bind(
220 &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
224 bool ContentVerifier::ShouldVerifyAnyPaths(
225 const std::string& extension_id,
226 const base::FilePath& extension_root,
227 const std::set<base::FilePath>& relative_paths) {
228 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
229 const ContentVerifierIOData::ExtensionData* data =
230 io_data_->GetData(extension_id);
231 if (!data)
232 return false;
234 const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
236 base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
237 scoped_ptr<std::set<std::string> > all_locales;
239 for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
240 i != relative_paths.end();
241 ++i) {
242 const base::FilePath& relative_path = *i;
244 if (relative_path == base::FilePath(kManifestFilename))
245 continue;
247 if (ContainsKey(browser_images, relative_path))
248 continue;
250 base::FilePath full_path = extension_root.Append(relative_path);
251 if (locales_dir.IsParent(full_path)) {
252 if (!all_locales) {
253 // TODO(asargent) - see if we can cache this list longer to avoid
254 // having to fetch it more than once for a given run of the
255 // browser. Maybe it can never change at runtime? (Or if it can, maybe
256 // there is an event we can listen for to know to drop our cache).
257 all_locales.reset(new std::set<std::string>);
258 extension_l10n_util::GetAllLocales(all_locales.get());
261 // Since message catalogs get transcoded during installation, we want
262 // to skip those paths.
263 if (full_path.DirName().DirName() == locales_dir &&
264 !extension_l10n_util::ShouldSkipValidation(
265 locales_dir, full_path.DirName(), *all_locales))
266 continue;
268 return true;
270 return false;
273 } // namespace extensions