Durable Storage: Refactor browser test and test the basic "deny" flow.
[chromium-blink-merge.git] / extensions / browser / content_verifier.cc
blobeaafe8f403bb2175063aa12d9ab6fe82cbceca63
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 } // namespace
29 // static
30 void ContentVerifier::SetObserverForTests(TestObserver* observer) {
31 g_test_observer = observer;
34 ContentVerifier::ContentVerifier(content::BrowserContext* context,
35 ContentVerifierDelegate* delegate)
36 : shutdown_(false),
37 context_(context),
38 delegate_(delegate),
39 fetcher_(new ContentHashFetcher(
40 context,
41 delegate,
42 base::Bind(&ContentVerifier::OnFetchComplete, this))),
43 observer_(this),
44 io_data_(new ContentVerifierIOData) {
47 ContentVerifier::~ContentVerifier() {
50 void ContentVerifier::Start() {
51 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
52 observer_.Add(registry);
55 void ContentVerifier::Shutdown() {
56 shutdown_ = true;
57 content::BrowserThread::PostTask(
58 content::BrowserThread::IO,
59 FROM_HERE,
60 base::Bind(&ContentVerifierIOData::Clear, io_data_));
61 observer_.RemoveAll();
62 fetcher_.reset();
65 ContentVerifyJob* ContentVerifier::CreateJobFor(
66 const std::string& extension_id,
67 const base::FilePath& extension_root,
68 const base::FilePath& relative_path) {
69 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
71 const ContentVerifierIOData::ExtensionData* data =
72 io_data_->GetData(extension_id);
73 if (!data)
74 return NULL;
76 std::set<base::FilePath> paths;
77 paths.insert(relative_path);
78 if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
79 return NULL;
81 // TODO(asargent) - we can probably get some good performance wins by having
82 // a cache of ContentHashReader's that we hold onto past the end of each job.
83 return new ContentVerifyJob(
84 new ContentHashReader(extension_id, data->version, extension_root,
85 relative_path, delegate_->GetPublicKey()),
86 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
89 void ContentVerifier::VerifyFailed(const std::string& extension_id,
90 ContentVerifyJob::FailureReason reason) {
91 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
92 content::BrowserThread::PostTask(
93 content::BrowserThread::UI,
94 FROM_HERE,
95 base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
96 return;
98 if (shutdown_)
99 return;
101 VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
103 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
104 const Extension* extension =
105 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
107 if (!extension)
108 return;
110 if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
111 // If we failed because there were no hashes yet for this extension, just
112 // request some.
113 fetcher_->DoFetch(extension, true /* force */);
114 } else {
115 delegate_->VerifyFailed(extension_id, reason);
119 static base::FilePath MakeImagePathRelative(const base::FilePath& path) {
120 if (path.ReferencesParent())
121 return base::FilePath();
123 std::vector<base::FilePath::StringType> parts;
124 path.GetComponents(&parts);
125 if (parts.empty())
126 return base::FilePath();
128 // Remove the first component if it is '.' or '/' or '//'.
129 const base::FilePath::StringType separators(
130 base::FilePath::kSeparators, base::FilePath::kSeparatorsLength);
131 if (!parts[0].empty() &&
132 (parts[0] == base::FilePath::kCurrentDirectory ||
133 parts[0].find_first_not_of(separators) == std::string::npos))
134 parts.erase(parts.begin());
136 // Note that elsewhere we always normalize path separators to '/' so this
137 // should work for all platforms.
138 return base::FilePath(
139 base::JoinString(parts, base::FilePath::StringType(1, '/')));
142 void ContentVerifier::OnExtensionLoaded(
143 content::BrowserContext* browser_context,
144 const Extension* extension) {
145 if (shutdown_)
146 return;
148 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
149 if (mode != ContentVerifierDelegate::NONE) {
150 // The browser image paths from the extension may not be relative (eg
151 // they might have leading '/' or './'), so we strip those to make
152 // comparing to actual relative paths work later on.
153 std::set<base::FilePath> original_image_paths =
154 delegate_->GetBrowserImagePaths(extension);
156 scoped_ptr<std::set<base::FilePath>> image_paths(
157 new std::set<base::FilePath>);
158 for (const auto& path : original_image_paths) {
159 image_paths->insert(MakeImagePathRelative(path));
162 scoped_ptr<ContentVerifierIOData::ExtensionData> data(
163 new ContentVerifierIOData::ExtensionData(
164 image_paths.Pass(),
165 extension->version() ? *extension->version() : base::Version()));
166 content::BrowserThread::PostTask(content::BrowserThread::IO,
167 FROM_HERE,
168 base::Bind(&ContentVerifierIOData::AddData,
169 io_data_,
170 extension->id(),
171 base::Passed(&data)));
172 fetcher_->ExtensionLoaded(extension);
176 void ContentVerifier::OnExtensionUnloaded(
177 content::BrowserContext* browser_context,
178 const Extension* extension,
179 UnloadedExtensionInfo::Reason reason) {
180 if (shutdown_)
181 return;
182 content::BrowserThread::PostTask(
183 content::BrowserThread::IO,
184 FROM_HERE,
185 base::Bind(
186 &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
187 if (fetcher_)
188 fetcher_->ExtensionUnloaded(extension);
191 void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
192 bool shouldVerifyAnyPathsResult) {
193 if (shouldVerifyAnyPathsResult)
194 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
197 void ContentVerifier::OnFetchComplete(
198 const std::string& extension_id,
199 bool success,
200 bool was_force_check,
201 const std::set<base::FilePath>& hash_mismatch_paths) {
202 if (g_test_observer)
203 g_test_observer->OnFetchComplete(extension_id, success);
205 if (shutdown_)
206 return;
208 VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
210 ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
211 const Extension* extension =
212 registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
213 if (!delegate_ || !extension)
214 return;
216 ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
217 if (was_force_check && !success &&
218 mode == ContentVerifierDelegate::ENFORCE_STRICT) {
219 // We weren't able to get verified_contents.json or weren't able to compute
220 // hashes.
221 delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
222 } else {
223 content::BrowserThread::PostTaskAndReplyWithResult(
224 content::BrowserThread::IO,
225 FROM_HERE,
226 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
227 this,
228 extension_id,
229 extension->path(),
230 hash_mismatch_paths),
231 base::Bind(
232 &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
236 bool ContentVerifier::ShouldVerifyAnyPaths(
237 const std::string& extension_id,
238 const base::FilePath& extension_root,
239 const std::set<base::FilePath>& relative_paths) {
240 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
241 const ContentVerifierIOData::ExtensionData* data =
242 io_data_->GetData(extension_id);
243 if (!data)
244 return false;
246 const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
248 base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
249 scoped_ptr<std::set<std::string> > all_locales;
251 for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
252 i != relative_paths.end();
253 ++i) {
254 const base::FilePath& relative_path = *i;
256 if (relative_path == base::FilePath(kManifestFilename))
257 continue;
259 if (ContainsKey(browser_images, relative_path))
260 continue;
262 base::FilePath full_path = extension_root.Append(relative_path);
263 if (locales_dir.IsParent(full_path)) {
264 if (!all_locales) {
265 // TODO(asargent) - see if we can cache this list longer to avoid
266 // having to fetch it more than once for a given run of the
267 // browser. Maybe it can never change at runtime? (Or if it can, maybe
268 // there is an event we can listen for to know to drop our cache).
269 all_locales.reset(new std::set<std::string>);
270 extension_l10n_util::GetAllLocales(all_locales.get());
273 // Since message catalogs get transcoded during installation, we want
274 // to skip those paths.
275 if (full_path.DirName().DirName() == locales_dir &&
276 !extension_l10n_util::ShouldSkipValidation(
277 locales_dir, full_path.DirName(), *all_locales))
278 continue;
280 return true;
282 return false;
285 } // namespace extensions