• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "extensions/browser/content_verifier.h"
6 
7 #include <algorithm>
8 
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"
20 
21 namespace extensions {
22 
ContentVerifier(content::BrowserContext * context,ContentVerifierDelegate * delegate)23 ContentVerifier::ContentVerifier(content::BrowserContext* context,
24                                  ContentVerifierDelegate* delegate)
25     : shutdown_(false),
26       context_(context),
27       delegate_(delegate),
28       fetcher_(new ContentHashFetcher(
29           context,
30           delegate,
31           base::Bind(&ContentVerifier::OnFetchComplete, this))),
32       observer_(this),
33       io_data_(new ContentVerifierIOData) {
34 }
35 
~ContentVerifier()36 ContentVerifier::~ContentVerifier() {
37 }
38 
Start()39 void ContentVerifier::Start() {
40   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
41   observer_.Add(registry);
42 }
43 
Shutdown()44 void ContentVerifier::Shutdown() {
45   shutdown_ = true;
46   content::BrowserThread::PostTask(
47       content::BrowserThread::IO,
48       FROM_HERE,
49       base::Bind(&ContentVerifierIOData::Clear, io_data_));
50   observer_.RemoveAll();
51   fetcher_.reset();
52 }
53 
CreateJobFor(const std::string & extension_id,const base::FilePath & extension_root,const base::FilePath & relative_path)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);
59 
60   const ContentVerifierIOData::ExtensionData* data =
61       io_data_->GetData(extension_id);
62   if (!data)
63     return NULL;
64 
65   std::set<base::FilePath> paths;
66   paths.insert(relative_path);
67   if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
68     return NULL;
69 
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,
74                             data->version,
75                             extension_root,
76                             relative_path,
77                             delegate_->PublicKey()),
78       base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
79 }
80 
VerifyFailed(const std::string & extension_id,ContentVerifyJob::FailureReason reason)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,
86         FROM_HERE,
87         base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
88     return;
89   }
90   if (shutdown_)
91     return;
92 
93   VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
94 
95   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
96   const Extension* extension =
97       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
98 
99   if (!extension)
100     return;
101 
102   if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
103     // If we failed because there were no hashes yet for this extension, just
104     // request some.
105     fetcher_->DoFetch(extension, true /* force */);
106   } else {
107     delegate_->VerifyFailed(extension_id, reason);
108   }
109 }
110 
MakeImagePathRelative(const base::FilePath & path)111 static base::FilePath MakeImagePathRelative(const base::FilePath& path) {
112   if (path.ReferencesParent())
113     return base::FilePath();
114 
115   std::vector<base::FilePath::StringType> parts;
116   path.GetComponents(&parts);
117   if (parts.empty())
118     return base::FilePath();
119 
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());
127 
128   // Note that elsewhere we always normalize path separators to '/' so this
129   // should work for all platforms.
130   return base::FilePath(JoinString(parts, '/'));
131 }
132 
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)133 void ContentVerifier::OnExtensionLoaded(
134     content::BrowserContext* browser_context,
135     const Extension* extension) {
136   if (shutdown_)
137     return;
138 
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);
146 
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));
151     }
152 
153     scoped_ptr<ContentVerifierIOData::ExtensionData> data(
154         new ContentVerifierIOData::ExtensionData(
155             image_paths.Pass(),
156             extension->version() ? *extension->version() : base::Version()));
157     content::BrowserThread::PostTask(content::BrowserThread::IO,
158                                      FROM_HERE,
159                                      base::Bind(&ContentVerifierIOData::AddData,
160                                                 io_data_,
161                                                 extension->id(),
162                                                 base::Passed(&data)));
163     fetcher_->ExtensionLoaded(extension);
164   }
165 }
166 
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)167 void ContentVerifier::OnExtensionUnloaded(
168     content::BrowserContext* browser_context,
169     const Extension* extension,
170     UnloadedExtensionInfo::Reason reason) {
171   if (shutdown_)
172     return;
173   content::BrowserThread::PostTask(
174       content::BrowserThread::IO,
175       FROM_HERE,
176       base::Bind(
177           &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
178   if (fetcher_)
179     fetcher_->ExtensionUnloaded(extension);
180 }
181 
OnFetchCompleteHelper(const std::string & extension_id,bool shouldVerifyAnyPathsResult)182 void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
183                                             bool shouldVerifyAnyPathsResult) {
184   if (shouldVerifyAnyPathsResult)
185     delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
186 }
187 
OnFetchComplete(const std::string & extension_id,bool success,bool was_force_check,const std::set<base::FilePath> & hash_mismatch_paths)188 void ContentVerifier::OnFetchComplete(
189     const std::string& extension_id,
190     bool success,
191     bool was_force_check,
192     const std::set<base::FilePath>& hash_mismatch_paths) {
193   if (shutdown_)
194     return;
195 
196   VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
197 
198   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
199   const Extension* extension =
200       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
201   if (!delegate_ || !extension)
202     return;
203 
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
208     // hashes.
209     delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
210   } else {
211     content::BrowserThread::PostTaskAndReplyWithResult(
212         content::BrowserThread::IO,
213         FROM_HERE,
214         base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
215                    this,
216                    extension_id,
217                    extension->path(),
218                    hash_mismatch_paths),
219         base::Bind(
220             &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
221   }
222 }
223 
ShouldVerifyAnyPaths(const std::string & extension_id,const base::FilePath & extension_root,const std::set<base::FilePath> & relative_paths)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);
231   if (!data)
232     return false;
233 
234   const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
235 
236   base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
237   scoped_ptr<std::set<std::string> > all_locales;
238 
239   for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
240        i != relative_paths.end();
241        ++i) {
242     const base::FilePath& relative_path = *i;
243 
244     if (relative_path == base::FilePath(kManifestFilename))
245       continue;
246 
247     if (ContainsKey(browser_images, relative_path))
248       continue;
249 
250     base::FilePath full_path = extension_root.Append(relative_path);
251     if (locales_dir.IsParent(full_path)) {
252       if (!all_locales) {
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());
259       }
260 
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))
266         continue;
267     }
268     return true;
269   }
270   return false;
271 }
272 
273 }  // namespace extensions
274