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
;
23 ContentVerifyJob::TestObserver
* g_test_observer
= NULL
;
25 class ScopedElapsedTimer
{
27 ScopedElapsedTimer(base::TimeDelta
* total
) : total_(total
) { DCHECK(total_
); }
29 ~ScopedElapsedTimer() { *total_
+= timer
.Elapsed(); }
32 // Some total amount of time we should add our elapsed time to at
34 base::TimeDelta
* total_
;
36 // A timer for how long this object has been alive.
37 base::ElapsedTimer timer
;
42 ContentVerifyJob::ContentVerifyJob(ContentHashReader
* hash_reader
,
43 const FailureCallback
& failure_callback
)
44 : done_reading_(false),
48 current_hash_byte_count_(0),
49 hash_reader_(hash_reader
),
50 failure_callback_(failure_callback
),
52 // It's ok for this object to be constructed on a different thread from where
54 thread_checker_
.DetachFromThread();
57 ContentVerifyJob::~ContentVerifyJob() {
58 UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
59 time_spent_
.InMicroseconds());
62 void ContentVerifyJob::Start() {
63 DCHECK(thread_checker_
.CalledOnValidThread());
65 g_test_observer
->JobStarted(hash_reader_
->extension_id(),
66 hash_reader_
->relative_path());
67 base::PostTaskAndReplyWithResult(
68 content::BrowserThread::GetBlockingPool(),
70 base::Bind(&ContentHashReader::Init
, hash_reader_
),
71 base::Bind(&ContentVerifyJob::OnHashesReady
, this));
74 void ContentVerifyJob::BytesRead(int count
, const char* data
) {
75 ScopedElapsedTimer
timer(&time_spent_
);
76 DCHECK(thread_checker_
.CalledOnValidThread());
79 if (g_test_delegate
) {
80 FailureReason reason
=
81 g_test_delegate
->BytesRead(hash_reader_
->extension_id(), count
, data
);
83 return DispatchFailureCallback(reason
);
86 queue_
.append(data
, count
);
92 while (bytes_added
< count
) {
93 if (current_block_
>= hash_reader_
->block_count())
94 return DispatchFailureCallback(HASH_MISMATCH
);
96 if (!current_hash_
.get()) {
97 current_hash_byte_count_
= 0;
99 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
101 // Compute how many bytes we should hash, and add them to the current hash.
103 std::min(hash_reader_
->block_size() - current_hash_byte_count_
,
104 count
- bytes_added
);
105 DCHECK(bytes_to_hash
> 0);
106 current_hash_
->Update(data
+ bytes_added
, bytes_to_hash
);
107 bytes_added
+= bytes_to_hash
;
108 current_hash_byte_count_
+= bytes_to_hash
;
109 total_bytes_read_
+= bytes_to_hash
;
111 // If we finished reading a block worth of data, finish computing the hash
112 // for it and make sure the expected hash matches.
113 if (current_hash_byte_count_
== hash_reader_
->block_size() &&
115 DispatchFailureCallback(HASH_MISMATCH
);
121 void ContentVerifyJob::DoneReading() {
122 ScopedElapsedTimer
timer(&time_spent_
);
123 DCHECK(thread_checker_
.CalledOnValidThread());
126 if (g_test_delegate
) {
127 FailureReason reason
=
128 g_test_delegate
->DoneReading(hash_reader_
->extension_id());
129 if (reason
!= NONE
) {
130 DispatchFailureCallback(reason
);
134 done_reading_
= true;
137 DispatchFailureCallback(HASH_MISMATCH
);
138 else if (g_test_observer
)
139 g_test_observer
->JobFinished(hash_reader_
->extension_id(),
140 hash_reader_
->relative_path(), failed_
);
144 bool ContentVerifyJob::FinishBlock() {
145 if (current_hash_byte_count_
<= 0)
147 std::string
final(crypto::kSHA256Length
, 0);
148 current_hash_
->Finish(string_as_array(&final
), final
.size());
149 current_hash_
.reset();
150 current_hash_byte_count_
= 0;
152 int block
= current_block_
++;
154 const std::string
* expected_hash
= NULL
;
155 if (!hash_reader_
->GetHashForBlock(block
, &expected_hash
) ||
156 *expected_hash
!= final
)
162 void ContentVerifyJob::OnHashesReady(bool success
) {
163 if (!success
&& !g_test_delegate
) {
164 if (!hash_reader_
->content_exists()) {
165 // Ignore verification of non-existent resources.
167 } else if (hash_reader_
->have_verified_contents() &&
168 hash_reader_
->have_computed_hashes()) {
169 DispatchFailureCallback(NO_HASHES_FOR_FILE
);
171 DispatchFailureCallback(MISSING_ALL_HASHES
);
176 hashes_ready_
= true;
177 if (!queue_
.empty()) {
180 BytesRead(tmp
.size(), string_as_array(&tmp
));
183 ScopedElapsedTimer
timer(&time_spent_
);
185 DispatchFailureCallback(HASH_MISMATCH
);
186 else if (g_test_observer
) {
187 g_test_observer
->JobFinished(hash_reader_
->extension_id(),
188 hash_reader_
->relative_path(), failed_
);
194 void ContentVerifyJob::SetDelegateForTests(TestDelegate
* delegate
) {
195 g_test_delegate
= delegate
;
199 void ContentVerifyJob::SetObserverForTests(TestObserver
* observer
) {
200 g_test_observer
= observer
;
203 void ContentVerifyJob::DispatchFailureCallback(FailureReason reason
) {
206 if (!failure_callback_
.is_null()) {
207 VLOG(1) << "job failed for " << hash_reader_
->extension_id() << " "
208 << hash_reader_
->relative_path().MaybeAsASCII()
209 << " reason:" << reason
;
210 failure_callback_
.Run(reason
);
211 failure_callback_
.Reset();
214 g_test_observer
->JobFinished(
215 hash_reader_
->extension_id(), hash_reader_
->relative_path(), failed_
);
218 } // namespace extensions