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
21namespace extensions {
22
23ContentVerifier::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
36ContentVerifier::~ContentVerifier() {
37}
38
39void ContentVerifier::Start() {
40  ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
41  observer_.Add(registry);
42}
43
44void 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
54ContentVerifyJob* 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
81void 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
111static 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
133void 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
167void 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
182void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
183                                            bool shouldVerifyAnyPathsResult) {
184  if (shouldVerifyAnyPathsResult)
185    delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
186}
187
188void 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
224bool 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