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_verify_job.h"
9 #include "base/metrics/histogram.h"
10 #include "base/stl_util.h"
11 #include "base/task_runner_util.h"
12 #include "base/timer/elapsed_timer.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "crypto/secure_hash.h"
15 #include "crypto/sha2.h"
16 #include "extensions/browser/content_hash_reader.h"
18 namespace extensions {
22 ContentVerifyJob::TestDelegate* g_test_delegate = NULL;
24 class ScopedElapsedTimer {
26 ScopedElapsedTimer(base::TimeDelta* total) : total_(total) { DCHECK(total_); }
28 ~ScopedElapsedTimer() { *total_ += timer.Elapsed(); }
31 // Some total amount of time we should add our elapsed time to at
33 base::TimeDelta* total_;
35 // A timer for how long this object has been alive.
36 base::ElapsedTimer timer;
41 ContentVerifyJob::ContentVerifyJob(ContentHashReader* hash_reader,
42 const FailureCallback& failure_callback)
43 : done_reading_(false),
47 current_hash_byte_count_(0),
48 hash_reader_(hash_reader),
49 failure_callback_(failure_callback),
51 // It's ok for this object to be constructed on a different thread from where
53 thread_checker_.DetachFromThread();
56 ContentVerifyJob::~ContentVerifyJob() {
57 UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
58 time_spent_.InMicroseconds());
61 void ContentVerifyJob::Start() {
62 DCHECK(thread_checker_.CalledOnValidThread());
63 base::PostTaskAndReplyWithResult(
64 content::BrowserThread::GetBlockingPool(),
66 base::Bind(&ContentHashReader::Init, hash_reader_),
67 base::Bind(&ContentVerifyJob::OnHashesReady, this));
70 void ContentVerifyJob::BytesRead(int count, const char* data) {
71 ScopedElapsedTimer timer(&time_spent_);
72 DCHECK(thread_checker_.CalledOnValidThread());
75 if (g_test_delegate) {
76 FailureReason reason =
77 g_test_delegate->BytesRead(hash_reader_->extension_id(), count, data);
79 return DispatchFailureCallback(reason);
82 queue_.append(data, count);
88 while (bytes_added < count) {
89 if (current_block_ >= hash_reader_->block_count())
90 return DispatchFailureCallback(HASH_MISMATCH);
92 if (!current_hash_.get()) {
93 current_hash_byte_count_ = 0;
95 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
97 // Compute how many bytes we should hash, and add them to the current hash.
99 std::min(hash_reader_->block_size() - current_hash_byte_count_,
100 count - bytes_added);
101 DCHECK(bytes_to_hash > 0);
102 current_hash_->Update(data + bytes_added, bytes_to_hash);
103 bytes_added += bytes_to_hash;
104 current_hash_byte_count_ += bytes_to_hash;
105 total_bytes_read_ += bytes_to_hash;
107 // If we finished reading a block worth of data, finish computing the hash
108 // for it and make sure the expected hash matches.
109 if (current_hash_byte_count_ == hash_reader_->block_size() &&
111 DispatchFailureCallback(HASH_MISMATCH);
117 void ContentVerifyJob::DoneReading() {
118 ScopedElapsedTimer timer(&time_spent_);
119 DCHECK(thread_checker_.CalledOnValidThread());
122 if (g_test_delegate) {
123 FailureReason reason =
124 g_test_delegate->DoneReading(hash_reader_->extension_id());
125 if (reason != NONE) {
126 DispatchFailureCallback(reason);
130 done_reading_ = true;
131 if (hashes_ready_ && !FinishBlock())
132 DispatchFailureCallback(HASH_MISMATCH);
135 bool ContentVerifyJob::FinishBlock() {
136 if (current_hash_byte_count_ <= 0)
138 std::string final(crypto::kSHA256Length, 0);
139 current_hash_->Finish(string_as_array(&final), final.size());
140 current_hash_.reset();
141 current_hash_byte_count_ = 0;
143 int block = current_block_++;
145 const std::string* expected_hash = NULL;
146 if (!hash_reader_->GetHashForBlock(block, &expected_hash) ||
147 *expected_hash != final)
153 void ContentVerifyJob::OnHashesReady(bool success) {
154 if (!success && !g_test_delegate) {
155 if (!hash_reader_->content_exists()) {
156 // Ignore verification of non-existent resources.
158 } else if (hash_reader_->have_verified_contents() &&
159 hash_reader_->have_computed_hashes()) {
160 DispatchFailureCallback(NO_HASHES_FOR_FILE);
162 DispatchFailureCallback(MISSING_ALL_HASHES);
167 hashes_ready_ = true;
168 if (!queue_.empty()) {
171 BytesRead(tmp.size(), string_as_array(&tmp));
174 ScopedElapsedTimer timer(&time_spent_);
176 DispatchFailureCallback(HASH_MISMATCH);
181 void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) {
182 g_test_delegate = delegate;
185 void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) {
188 if (!failure_callback_.is_null()) {
189 VLOG(1) << "job failed for " << hash_reader_->extension_id() << " "
190 << hash_reader_->relative_path().MaybeAsASCII()
191 << " reason:" << reason;
192 failure_callback_.Run(reason);
193 failure_callback_.Reset();
197 } // namespace extensions