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"
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
{
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
);
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, '/')));
55 void ContentVerifier::SetObserverForTests(TestObserver
* observer
) {
56 g_test_observer
= observer
;
59 ContentVerifier::ContentVerifier(content::BrowserContext
* context
,
60 ContentVerifierDelegate
* delegate
)
64 fetcher_(new ContentHashFetcher(
67 base::Bind(&ContentVerifier::OnFetchComplete
, 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() {
82 content::BrowserThread::PostTask(
83 content::BrowserThread::IO
,
85 base::Bind(&ContentVerifierIOData::Clear
, io_data_
));
86 observer_
.RemoveAll();
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
);
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
))
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
,
122 base::Bind(&ContentVerifier::VerifyFailed
, this, extension_id
, reason
));
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
);
137 if (reason
== ContentVerifyJob::MISSING_ALL_HASHES
) {
138 // If we failed because there were no hashes yet for this extension, just
140 fetcher_
->DoFetch(extension
, true /* force */);
142 delegate_
->VerifyFailed(extension_id
, reason
);
146 void ContentVerifier::OnExtensionLoaded(
147 content::BrowserContext
* browser_context
,
148 const Extension
* extension
) {
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(
169 extension
->version() ? *extension
->version() : base::Version()));
170 content::BrowserThread::PostTask(content::BrowserThread::IO
,
172 base::Bind(&ContentVerifierIOData::AddData
,
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
) {
186 content::BrowserThread::PostTask(
187 content::BrowserThread::IO
,
190 &ContentVerifierIOData::RemoveData
, io_data_
, extension
->id()));
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
,
204 bool was_force_check
,
205 const std::set
<base::FilePath
>& hash_mismatch_paths
) {
207 g_test_observer
->OnFetchComplete(extension_id
, success
);
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
)
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
225 delegate_
->VerifyFailed(extension_id
, ContentVerifyJob::MISSING_ALL_HASHES
);
227 content::BrowserThread::PostTaskAndReplyWithResult(
228 content::BrowserThread::IO
,
230 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths
,
234 hash_mismatch_paths
),
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
);
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();
258 const base::FilePath
& relative_path
= *i
;
260 if (relative_path
== base::FilePath(kManifestFilename
))
263 if (ContainsKey(browser_images
, relative_path
))
266 base::FilePath full_path
= extension_root
.Append(relative_path
);
267 if (locales_dir
.IsParent(full_path
)) {
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
))
289 } // namespace extensions