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