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
, data
->version
, extension_root
,
74 relative_path
, delegate_
->GetPublicKey()),
75 base::Bind(&ContentVerifier::VerifyFailed
, this, extension_id
));
78 void ContentVerifier::VerifyFailed(const std::string
& extension_id
,
79 ContentVerifyJob::FailureReason reason
) {
80 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
)) {
81 content::BrowserThread::PostTask(
82 content::BrowserThread::UI
,
84 base::Bind(&ContentVerifier::VerifyFailed
, this, extension_id
, reason
));
90 VLOG(1) << "VerifyFailed " << extension_id
<< " reason:" << reason
;
92 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context_
);
93 const Extension
* extension
=
94 registry
->GetExtensionById(extension_id
, ExtensionRegistry::EVERYTHING
);
99 if (reason
== ContentVerifyJob::MISSING_ALL_HASHES
) {
100 // If we failed because there were no hashes yet for this extension, just
102 fetcher_
->DoFetch(extension
, true /* force */);
104 delegate_
->VerifyFailed(extension_id
, reason
);
108 static base::FilePath
MakeImagePathRelative(const base::FilePath
& path
) {
109 if (path
.ReferencesParent())
110 return base::FilePath();
112 std::vector
<base::FilePath::StringType
> parts
;
113 path
.GetComponents(&parts
);
115 return base::FilePath();
117 // Remove the first component if it is '.' or '/' or '//'.
118 const base::FilePath::StringType
separators(
119 base::FilePath::kSeparators
, base::FilePath::kSeparatorsLength
);
120 if (!parts
[0].empty() &&
121 (parts
[0] == base::FilePath::kCurrentDirectory
||
122 parts
[0].find_first_not_of(separators
) == std::string::npos
))
123 parts
.erase(parts
.begin());
125 // Note that elsewhere we always normalize path separators to '/' so this
126 // should work for all platforms.
127 return base::FilePath(
128 base::JoinString(parts
, base::FilePath::StringType(1, '/')));
131 void ContentVerifier::OnExtensionLoaded(
132 content::BrowserContext
* browser_context
,
133 const Extension
* extension
) {
137 ContentVerifierDelegate::Mode mode
= delegate_
->ShouldBeVerified(*extension
);
138 if (mode
!= ContentVerifierDelegate::NONE
) {
139 // The browser image paths from the extension may not be relative (eg
140 // they might have leading '/' or './'), so we strip those to make
141 // comparing to actual relative paths work later on.
142 std::set
<base::FilePath
> original_image_paths
=
143 delegate_
->GetBrowserImagePaths(extension
);
145 scoped_ptr
<std::set
<base::FilePath
>> image_paths(
146 new std::set
<base::FilePath
>);
147 for (const auto& path
: original_image_paths
) {
148 image_paths
->insert(MakeImagePathRelative(path
));
151 scoped_ptr
<ContentVerifierIOData::ExtensionData
> data(
152 new ContentVerifierIOData::ExtensionData(
154 extension
->version() ? *extension
->version() : base::Version()));
155 content::BrowserThread::PostTask(content::BrowserThread::IO
,
157 base::Bind(&ContentVerifierIOData::AddData
,
160 base::Passed(&data
)));
161 fetcher_
->ExtensionLoaded(extension
);
165 void ContentVerifier::OnExtensionUnloaded(
166 content::BrowserContext
* browser_context
,
167 const Extension
* extension
,
168 UnloadedExtensionInfo::Reason reason
) {
171 content::BrowserThread::PostTask(
172 content::BrowserThread::IO
,
175 &ContentVerifierIOData::RemoveData
, io_data_
, extension
->id()));
177 fetcher_
->ExtensionUnloaded(extension
);
180 void ContentVerifier::OnFetchCompleteHelper(const std::string
& extension_id
,
181 bool shouldVerifyAnyPathsResult
) {
182 if (shouldVerifyAnyPathsResult
)
183 delegate_
->VerifyFailed(extension_id
, ContentVerifyJob::MISSING_ALL_HASHES
);
186 void ContentVerifier::OnFetchComplete(
187 const std::string
& extension_id
,
189 bool was_force_check
,
190 const std::set
<base::FilePath
>& hash_mismatch_paths
) {
194 VLOG(1) << "OnFetchComplete " << extension_id
<< " success:" << success
;
196 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context_
);
197 const Extension
* extension
=
198 registry
->GetExtensionById(extension_id
, ExtensionRegistry::EVERYTHING
);
199 if (!delegate_
|| !extension
)
202 ContentVerifierDelegate::Mode mode
= delegate_
->ShouldBeVerified(*extension
);
203 if (was_force_check
&& !success
&&
204 mode
== ContentVerifierDelegate::ENFORCE_STRICT
) {
205 // We weren't able to get verified_contents.json or weren't able to compute
207 delegate_
->VerifyFailed(extension_id
, ContentVerifyJob::MISSING_ALL_HASHES
);
209 content::BrowserThread::PostTaskAndReplyWithResult(
210 content::BrowserThread::IO
,
212 base::Bind(&ContentVerifier::ShouldVerifyAnyPaths
,
216 hash_mismatch_paths
),
218 &ContentVerifier::OnFetchCompleteHelper
, this, extension_id
));
222 bool ContentVerifier::ShouldVerifyAnyPaths(
223 const std::string
& extension_id
,
224 const base::FilePath
& extension_root
,
225 const std::set
<base::FilePath
>& relative_paths
) {
226 DCHECK_CURRENTLY_ON(content::BrowserThread::IO
);
227 const ContentVerifierIOData::ExtensionData
* data
=
228 io_data_
->GetData(extension_id
);
232 const std::set
<base::FilePath
>& browser_images
= *(data
->browser_image_paths
);
234 base::FilePath locales_dir
= extension_root
.Append(kLocaleFolder
);
235 scoped_ptr
<std::set
<std::string
> > all_locales
;
237 for (std::set
<base::FilePath
>::const_iterator i
= relative_paths
.begin();
238 i
!= relative_paths
.end();
240 const base::FilePath
& relative_path
= *i
;
242 if (relative_path
== base::FilePath(kManifestFilename
))
245 if (ContainsKey(browser_images
, relative_path
))
248 base::FilePath full_path
= extension_root
.Append(relative_path
);
249 if (locales_dir
.IsParent(full_path
)) {
251 // TODO(asargent) - see if we can cache this list longer to avoid
252 // having to fetch it more than once for a given run of the
253 // browser. Maybe it can never change at runtime? (Or if it can, maybe
254 // there is an event we can listen for to know to drop our cache).
255 all_locales
.reset(new std::set
<std::string
>);
256 extension_l10n_util::GetAllLocales(all_locales
.get());
259 // Since message catalogs get transcoded during installation, we want
260 // to skip those paths.
261 if (full_path
.DirName().DirName() == locales_dir
&&
262 !extension_l10n_util::ShouldSkipValidation(
263 locales_dir
, full_path
.DirName(), *all_locales
))
271 } // namespace extensions