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
{
23 ContentVerifier::ContentVerifier(content::BrowserContext
* context
,
24 ContentVerifierDelegate
* delegate
)
28 fetcher_(new ContentHashFetcher(
31 base::Bind(&ContentVerifier::OnFetchComplete
, 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() {
46 content::BrowserThread::PostTask(
47 content::BrowserThread::IO
,
49 base::Bind(&ContentVerifierIOData::Clear
, io_data_
));
50 observer_
.RemoveAll();
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
);
65 std::set
<base::FilePath
> paths
;
66 paths
.insert(relative_path
);
67 if (!ShouldVerifyAnyPaths(extension_id
, extension_root
, paths
))
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
,
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
,
87 base::Bind(&ContentVerifier::VerifyFailed
, this, extension_id
, reason
));
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
);
102 if (reason
== ContentVerifyJob::MISSING_ALL_HASHES
) {
103 // If we failed because there were no hashes yet for this extension, just
105 fetcher_
->DoFetch(extension
, true /* force */);
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
);
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
) {
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(
156 extension
->version() ? *extension
->version() : base::Version()));
157 content::BrowserThread::PostTask(content::BrowserThread::IO
,
159 base::Bind(&ContentVerifierIOData::AddData
,
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
) {
173 content::BrowserThread::PostTask(
174 content::BrowserThread::IO
,
177 &ContentVerifierIOData::RemoveData
, io_data_
, extension
->id()));
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
,
191 bool was_force_check
,
192 const std::set
<base::FilePath
>& hash_mismatch_paths
) {
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
)
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
209 delegate_
->VerifyFailed(extension_id
, ContentVerifyJob::MISSING_ALL_HASHES
);
211 content::BrowserThread::PostTaskAndReplyWithResult(
212 content::BrowserThread::IO
,
214 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths
,
218 hash_mismatch_paths
),
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
);
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();
242 const base::FilePath
& relative_path
= *i
;
244 if (relative_path
== base::FilePath(kManifestFilename
))
247 if (ContainsKey(browser_images
, relative_path
))
250 base::FilePath full_path
= extension_root
.Append(relative_path
);
251 if (locales_dir
.IsParent(full_path
)) {
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
))
273 } // namespace extensions