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
;
30 void ContentVerifier::SetObserverForTests(TestObserver
* observer
) {
31 g_test_observer
= observer
;
34 ContentVerifier::ContentVerifier(content::BrowserContext
* context
,
35 ContentVerifierDelegate
* delegate
)
39 fetcher_(new ContentHashFetcher(
42 base::Bind(&ContentVerifier::OnFetchComplete
, 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() {
57 content::BrowserThread::PostTask(
58 content::BrowserThread::IO
,
60 base::Bind(&ContentVerifierIOData::Clear
, io_data_
));
61 observer_
.RemoveAll();
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
);
76 std::set
<base::FilePath
> paths
;
77 paths
.insert(relative_path
);
78 if (!ShouldVerifyAnyPaths(extension_id
, extension_root
, paths
))
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
,
95 base::Bind(&ContentVerifier::VerifyFailed
, this, extension_id
, reason
));
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
);
110 if (reason
== ContentVerifyJob::MISSING_ALL_HASHES
) {
111 // If we failed because there were no hashes yet for this extension, just
113 fetcher_
->DoFetch(extension
, true /* force */);
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
);
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
) {
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(
165 extension
->version() ? *extension
->version() : base::Version()));
166 content::BrowserThread::PostTask(content::BrowserThread::IO
,
168 base::Bind(&ContentVerifierIOData::AddData
,
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
) {
182 content::BrowserThread::PostTask(
183 content::BrowserThread::IO
,
186 &ContentVerifierIOData::RemoveData
, io_data_
, extension
->id()));
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
,
200 bool was_force_check
,
201 const std::set
<base::FilePath
>& hash_mismatch_paths
) {
203 g_test_observer
->OnFetchComplete(extension_id
, success
);
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
)
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
221 delegate_
->VerifyFailed(extension_id
, ContentVerifyJob::MISSING_ALL_HASHES
);
223 content::BrowserThread::PostTaskAndReplyWithResult(
224 content::BrowserThread::IO
,
226 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths
,
230 hash_mismatch_paths
),
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
);
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();
254 const base::FilePath
& relative_path
= *i
;
256 if (relative_path
== base::FilePath(kManifestFilename
))
259 if (ContainsKey(browser_images
, relative_path
))
262 base::FilePath full_path
= extension_root
.Append(relative_path
);
263 if (locales_dir
.IsParent(full_path
)) {
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
))
285 } // namespace extensions