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_hash_fetcher.h"
9 #include "base/base64.h"
10 #include "base/file_util.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/json/json_reader.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/stl_util.h"
15 #include "base/synchronization/lock.h"
16 #include "base/task_runner_util.h"
17 #include "base/version.h"
18 #include "content/public/browser/browser_context.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "crypto/secure_hash.h"
21 #include "crypto/sha2.h"
22 #include "extensions/browser/computed_hashes.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/common/constants.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/file_util.h"
27 #include "net/base/load_flags.h"
28 #include "net/url_request/url_fetcher.h"
29 #include "net/url_request/url_fetcher_delegate.h"
30 #include "net/url_request/url_request_status.h"
34 typedef std::set
<base::FilePath
> SortedFilePathSet
;
38 namespace extensions
{
40 // This class takes care of doing the disk and network I/O work to ensure we
41 // have both verified_contents.json files from the webstore and
42 // computed_hashes.json files computed over the files in an extension's
44 class ContentHashFetcherJob
45 : public base::RefCountedThreadSafe
<ContentHashFetcherJob
>,
46 public net::URLFetcherDelegate
{
48 typedef base::Callback
<void(ContentHashFetcherJob
*)> CompletionCallback
;
49 ContentHashFetcherJob(net::URLRequestContextGetter
* request_context
,
50 const std::string
& extension_id
,
51 const base::FilePath
& extension_path
,
52 const GURL
& fetch_url
,
53 const CompletionCallback
& callback
);
57 // Cancels this job, which will attempt to stop I/O operations sooner than
58 // just waiting for the entire job to complete. Safe to call from any thread.
61 // Returns whether this job was completely successful (we have both verified
62 // contents and computed hashes).
63 bool success() { return success_
; }
65 // Do we have a verified_contents.json file?
66 bool have_verified_contents() { return have_verified_contents_
; }
69 friend class base::RefCountedThreadSafe
<ContentHashFetcherJob
>;
70 virtual ~ContentHashFetcherJob();
72 // Checks whether this job has been cancelled. Safe to call from any thread.
75 // Callback for when we're done doing file I/O to see if we already have
76 // a verified contents file. If we don't, this will kick off a network
77 // request to get one.
78 void DoneCheckingForVerifiedContents(bool found
);
80 // URLFetcherDelegate interface
81 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
;
83 // Callback for when we're done ensuring we have verified contents, and are
84 // ready to move on to MaybeCreateHashes.
85 void DoneFetchingVerifiedContents(bool success
);
87 // Callback for the job to write the verified contents to the filesystem.
88 void OnVerifiedContentsWritten(size_t expected_size
, int write_result
);
90 // The verified contents file from the webstore only contains the treehash
91 // root hash, but for performance we want to cache the individual block level
92 // hashes. This function will create that cache with block-level hashes for
93 // each file in the extension if needed (the treehash root hash for each of
94 // these should equal what is in the verified contents file from the
96 void MaybeCreateHashes();
98 // Computes hashes for all files in |extension_path_|, and uses a
99 // ComputedHashes::Writer to write that information into
100 // |hashes_file|. Returns true on success.
101 bool CreateHashes(const base::FilePath
& hashes_file
);
103 // Will call the callback, if we haven't been cancelled.
104 void DispatchCallback();
106 net::URLRequestContextGetter
* request_context_
;
107 std::string extension_id_
;
108 base::FilePath extension_path_
;
110 // The url we'll need to use to fetch a verified_contents.json file.
113 CompletionCallback callback_
;
114 content::BrowserThread::ID creation_thread_
;
116 // Used for fetching content signatures.
117 scoped_ptr
<net::URLFetcher
> url_fetcher_
;
119 // Whether this job succeeded.
122 // Whether we either found a verified contents file, or were successful in
123 // fetching one and saving it to disk.
124 bool have_verified_contents_
;
126 // The block size to use for hashing.
129 // Note: this may be accessed from multiple threads, so all access should
130 // be protected by |cancelled_lock_|.
133 // A lock for synchronizing access to |cancelled_|.
134 base::Lock cancelled_lock_
;
137 ContentHashFetcherJob::ContentHashFetcherJob(
138 net::URLRequestContextGetter
* request_context
,
139 const std::string
& extension_id
,
140 const base::FilePath
& extension_path
,
141 const GURL
& fetch_url
,
142 const CompletionCallback
& callback
)
143 : request_context_(request_context
),
144 extension_id_(extension_id
),
145 extension_path_(extension_path
),
146 fetch_url_(fetch_url
),
149 have_verified_contents_(false),
150 // TODO(asargent) - use the value from verified_contents.json for each
151 // file, instead of using a constant.
155 content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_
);
159 void ContentHashFetcherJob::Start() {
160 base::FilePath verified_contents_path
=
161 file_util::GetVerifiedContentsPath(extension_path_
);
162 base::PostTaskAndReplyWithResult(
163 content::BrowserThread::GetBlockingPool(),
165 base::Bind(&base::PathExists
, verified_contents_path
),
166 base::Bind(&ContentHashFetcherJob::DoneCheckingForVerifiedContents
,
170 void ContentHashFetcherJob::Cancel() {
171 base::AutoLock
autolock(cancelled_lock_
);
175 ContentHashFetcherJob::~ContentHashFetcherJob() {
178 bool ContentHashFetcherJob::IsCancelled() {
179 base::AutoLock
autolock(cancelled_lock_
);
180 bool result
= cancelled_
;
184 void ContentHashFetcherJob::DoneCheckingForVerifiedContents(bool found
) {
188 DoneFetchingVerifiedContents(true);
191 net::URLFetcher::Create(fetch_url_
, net::URLFetcher::GET
, this));
192 url_fetcher_
->SetRequestContext(request_context_
);
193 url_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
194 net::LOAD_DO_NOT_SAVE_COOKIES
|
195 net::LOAD_DISABLE_CACHE
);
196 url_fetcher_
->SetAutomaticallyRetryOnNetworkChanges(3);
197 url_fetcher_
->Start();
201 // Helper function to let us pass ownership of a string via base::Bind with the
202 // contents to be written into a file. Also ensures that the directory for
203 // |path| exists, creating it if needed.
204 static int WriteFileHelper(const base::FilePath
& path
,
205 scoped_ptr
<std::string
> content
) {
206 base::FilePath dir
= path
.DirName();
207 return (base::CreateDirectoryAndGetError(dir
, NULL
) &&
208 base::WriteFile(path
, content
->data(), content
->size()));
211 void ContentHashFetcherJob::OnURLFetchComplete(const net::URLFetcher
* source
) {
214 scoped_ptr
<std::string
> response(new std::string
);
215 if (!url_fetcher_
->GetStatus().is_success() ||
216 !url_fetcher_
->GetResponseAsString(response
.get())) {
217 DoneFetchingVerifiedContents(false);
221 // Parse the response to make sure it is valid json (on staging sometimes it
222 // can be a login redirect html, xml file, etc. if you aren't logged in with
223 // the right cookies). TODO(asargent) - It would be a nice enhancement to
224 // move to parsing this in a sandboxed helper (crbug.com/372878).
225 scoped_ptr
<base::Value
> parsed(base::JSONReader::Read(*response
));
227 parsed
.reset(); // no longer needed
228 base::FilePath destination
=
229 file_util::GetVerifiedContentsPath(extension_path_
);
230 size_t size
= response
->size();
231 base::PostTaskAndReplyWithResult(
232 content::BrowserThread::GetBlockingPool(),
234 base::Bind(&WriteFileHelper
, destination
, base::Passed(&response
)),
236 &ContentHashFetcherJob::OnVerifiedContentsWritten
, this, size
));
238 DoneFetchingVerifiedContents(false);
242 void ContentHashFetcherJob::OnVerifiedContentsWritten(size_t expected_size
,
245 (write_result
>= 0 && static_cast<size_t>(write_result
) == expected_size
);
246 DoneFetchingVerifiedContents(success
);
249 void ContentHashFetcherJob::DoneFetchingVerifiedContents(bool success
) {
250 have_verified_contents_
= success
;
255 // TODO(asargent) - eventually we should abort here on !success, but for
256 // testing purposes it's actually still helpful to continue on to create the
259 content::BrowserThread::PostBlockingPoolSequencedTask(
260 "ContentHashFetcher",
262 base::Bind(&ContentHashFetcherJob::MaybeCreateHashes
, this));
265 void ContentHashFetcherJob::MaybeCreateHashes() {
268 base::FilePath hashes_file
=
269 file_util::GetComputedHashesPath(extension_path_
);
271 if (base::PathExists(hashes_file
))
274 success_
= CreateHashes(hashes_file
);
276 content::BrowserThread::PostTask(
279 base::Bind(&ContentHashFetcherJob::DispatchCallback
, this));
282 bool ContentHashFetcherJob::CreateHashes(const base::FilePath
& hashes_file
) {
285 // Make sure the directory exists.
286 if (!base::CreateDirectoryAndGetError(hashes_file
.DirName(), NULL
))
289 base::FileEnumerator
enumerator(extension_path_
,
290 true, /* recursive */
291 base::FileEnumerator::FILES
);
292 // First discover all the file paths and put them in a sorted set.
293 SortedFilePathSet paths
;
298 base::FilePath full_path
= enumerator
.Next();
299 if (full_path
.empty())
301 paths
.insert(full_path
);
304 // Now iterate over all the paths in sorted order and compute the block hashes
306 ComputedHashes::Writer writer
;
307 for (SortedFilePathSet::iterator i
= paths
.begin(); i
!= paths
.end(); ++i
) {
310 const base::FilePath
& full_path
= *i
;
311 base::FilePath relative_path
;
312 extension_path_
.AppendRelativePath(full_path
, &relative_path
);
313 std::string contents
;
314 if (!base::ReadFileToString(full_path
, &contents
)) {
315 LOG(ERROR
) << "Could not read " << full_path
.MaybeAsASCII();
319 // Iterate through taking the hash of each block of size (block_size_) of
321 std::vector
<std::string
> hashes
;
323 while (offset
< contents
.size()) {
326 const char* block_start
= contents
.data() + offset
;
327 size_t bytes_to_read
=
328 std::min(contents
.size() - offset
, static_cast<size_t>(block_size_
));
329 DCHECK(bytes_to_read
> 0);
330 scoped_ptr
<crypto::SecureHash
> hash(
331 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
332 hash
->Update(block_start
, bytes_to_read
);
334 hashes
.push_back(std::string());
335 std::string
* buffer
= &hashes
.back();
336 buffer
->resize(crypto::kSHA256Length
);
337 hash
->Finish(string_as_array(buffer
), buffer
->size());
339 // Get ready for next iteration.
340 offset
+= bytes_to_read
;
342 writer
.AddHashes(relative_path
, block_size_
, hashes
);
344 return writer
.WriteToFile(hashes_file
);
347 void ContentHashFetcherJob::DispatchCallback() {
349 base::AutoLock
autolock(cancelled_lock_
);
358 ContentHashFetcher::ContentHashFetcher(content::BrowserContext
* context
,
359 ContentVerifierDelegate
* delegate
)
363 weak_ptr_factory_(this) {
366 ContentHashFetcher::~ContentHashFetcher() {
367 for (JobMap::iterator i
= jobs_
.begin(); i
!= jobs_
.end(); ++i
) {
372 void ContentHashFetcher::Start() {
373 ExtensionRegistry
* registry
= ExtensionRegistry::Get(context_
);
374 observer_
.Add(registry
);
377 void ContentHashFetcher::DoFetch(const Extension
* extension
) {
378 if (!extension
|| !delegate_
->ShouldBeVerified(*extension
))
381 IdAndVersion
key(extension
->id(), extension
->version()->GetString());
382 if (ContainsKey(jobs_
, key
))
385 // TODO(asargent) - we should do something here to remember recent attempts
386 // to fetch signatures by extension id, and use exponential backoff to avoid
387 // hammering the server when we aren't successful in getting them.
390 DCHECK(extension
->version());
392 delegate_
->GetSignatureFetchUrl(extension
->id(), *extension
->version());
393 ContentHashFetcherJob
* job
=
394 new ContentHashFetcherJob(context_
->GetRequestContext(),
398 base::Bind(&ContentHashFetcher::JobFinished
,
399 weak_ptr_factory_
.GetWeakPtr()));
400 jobs_
.insert(std::make_pair(key
, job
));
404 void ContentHashFetcher::OnExtensionLoaded(
405 content::BrowserContext
* browser_context
,
406 const Extension
* extension
) {
411 void ContentHashFetcher::OnExtensionUnloaded(
412 content::BrowserContext
* browser_context
,
413 const Extension
* extension
,
414 UnloadedExtensionInfo::Reason reason
) {
416 IdAndVersion
key(extension
->id(), extension
->version()->GetString());
417 JobMap::iterator found
= jobs_
.find(key
);
418 if (found
!= jobs_
.end())
422 void ContentHashFetcher::JobFinished(ContentHashFetcherJob
* job
) {
423 for (JobMap::iterator i
= jobs_
.begin(); i
!= jobs_
.end(); ++i
) {
424 if (i
->second
.get() == job
) {
431 } // namespace extensions