• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_verify_job.h"
6 
7 #include <algorithm>
8 
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"
17 
18 namespace extensions {
19 
20 namespace {
21 
22 ContentVerifyJob::TestDelegate* g_test_delegate = NULL;
23 ContentVerifyJob::TestObserver* g_test_observer = NULL;
24 
25 class ScopedElapsedTimer {
26  public:
ScopedElapsedTimer(base::TimeDelta * total)27   ScopedElapsedTimer(base::TimeDelta* total) : total_(total) { DCHECK(total_); }
28 
~ScopedElapsedTimer()29   ~ScopedElapsedTimer() { *total_ += timer.Elapsed(); }
30 
31  private:
32   // Some total amount of time we should add our elapsed time to at
33   // destruction.
34   base::TimeDelta* total_;
35 
36   // A timer for how long this object has been alive.
37   base::ElapsedTimer timer;
38 };
39 
40 }  // namespace
41 
ContentVerifyJob(ContentHashReader * hash_reader,const FailureCallback & failure_callback)42 ContentVerifyJob::ContentVerifyJob(ContentHashReader* hash_reader,
43                                    const FailureCallback& failure_callback)
44     : done_reading_(false),
45       hashes_ready_(false),
46       total_bytes_read_(0),
47       current_block_(0),
48       current_hash_byte_count_(0),
49       hash_reader_(hash_reader),
50       failure_callback_(failure_callback),
51       failed_(false) {
52   // It's ok for this object to be constructed on a different thread from where
53   // it's used.
54   thread_checker_.DetachFromThread();
55 }
56 
~ContentVerifyJob()57 ContentVerifyJob::~ContentVerifyJob() {
58   UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
59                        time_spent_.InMicroseconds());
60 }
61 
Start()62 void ContentVerifyJob::Start() {
63   DCHECK(thread_checker_.CalledOnValidThread());
64   if (g_test_observer)
65     g_test_observer->JobStarted(hash_reader_->extension_id(),
66                                 hash_reader_->relative_path());
67   base::PostTaskAndReplyWithResult(
68       content::BrowserThread::GetBlockingPool(),
69       FROM_HERE,
70       base::Bind(&ContentHashReader::Init, hash_reader_),
71       base::Bind(&ContentVerifyJob::OnHashesReady, this));
72 }
73 
BytesRead(int count,const char * data)74 void ContentVerifyJob::BytesRead(int count, const char* data) {
75   ScopedElapsedTimer timer(&time_spent_);
76   DCHECK(thread_checker_.CalledOnValidThread());
77   if (failed_)
78     return;
79   if (g_test_delegate) {
80     FailureReason reason =
81         g_test_delegate->BytesRead(hash_reader_->extension_id(), count, data);
82     if (reason != NONE)
83       return DispatchFailureCallback(reason);
84   }
85   if (!hashes_ready_) {
86     queue_.append(data, count);
87     return;
88   }
89   DCHECK_GE(count, 0);
90   int bytes_added = 0;
91 
92   while (bytes_added < count) {
93     if (current_block_ >= hash_reader_->block_count())
94       return DispatchFailureCallback(HASH_MISMATCH);
95 
96     if (!current_hash_.get()) {
97       current_hash_byte_count_ = 0;
98       current_hash_.reset(
99           crypto::SecureHash::Create(crypto::SecureHash::SHA256));
100     }
101     // Compute how many bytes we should hash, and add them to the current hash.
102     int bytes_to_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;
110 
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() &&
114         !FinishBlock()) {
115       DispatchFailureCallback(HASH_MISMATCH);
116       return;
117     }
118   }
119 }
120 
DoneReading()121 void ContentVerifyJob::DoneReading() {
122   ScopedElapsedTimer timer(&time_spent_);
123   DCHECK(thread_checker_.CalledOnValidThread());
124   if (failed_)
125     return;
126   if (g_test_delegate) {
127     FailureReason reason =
128         g_test_delegate->DoneReading(hash_reader_->extension_id());
129     if (reason != NONE) {
130       DispatchFailureCallback(reason);
131       return;
132     }
133   }
134   done_reading_ = true;
135   if (hashes_ready_ && !FinishBlock())
136     DispatchFailureCallback(HASH_MISMATCH);
137 
138   if (!failed_ && g_test_observer)
139     g_test_observer->JobFinished(
140         hash_reader_->extension_id(), hash_reader_->relative_path(), failed_);
141 }
142 
FinishBlock()143 bool ContentVerifyJob::FinishBlock() {
144   if (current_hash_byte_count_ <= 0)
145     return true;
146   std::string final(crypto::kSHA256Length, 0);
147   current_hash_->Finish(string_as_array(&final), final.size());
148   current_hash_.reset();
149   current_hash_byte_count_ = 0;
150 
151   int block = current_block_++;
152 
153   const std::string* expected_hash = NULL;
154   if (!hash_reader_->GetHashForBlock(block, &expected_hash) ||
155       *expected_hash != final)
156     return false;
157 
158   return true;
159 }
160 
OnHashesReady(bool success)161 void ContentVerifyJob::OnHashesReady(bool success) {
162   if (!success && !g_test_delegate) {
163     if (!hash_reader_->content_exists()) {
164       // Ignore verification of non-existent resources.
165       return;
166     } else if (hash_reader_->have_verified_contents() &&
167                hash_reader_->have_computed_hashes()) {
168       DispatchFailureCallback(NO_HASHES_FOR_FILE);
169     } else {
170       DispatchFailureCallback(MISSING_ALL_HASHES);
171     }
172     return;
173   }
174 
175   hashes_ready_ = true;
176   if (!queue_.empty()) {
177     std::string tmp;
178     queue_.swap(tmp);
179     BytesRead(tmp.size(), string_as_array(&tmp));
180   }
181   if (done_reading_) {
182     ScopedElapsedTimer timer(&time_spent_);
183     if (!FinishBlock())
184       DispatchFailureCallback(HASH_MISMATCH);
185   }
186 }
187 
188 // static
SetDelegateForTests(TestDelegate * delegate)189 void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) {
190   g_test_delegate = delegate;
191 }
192 
193 // static
SetObserverForTests(TestObserver * observer)194 void ContentVerifyJob::SetObserverForTests(TestObserver* observer) {
195   g_test_observer = observer;
196 }
197 
DispatchFailureCallback(FailureReason reason)198 void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) {
199   DCHECK(!failed_);
200   failed_ = true;
201   if (!failure_callback_.is_null()) {
202     VLOG(1) << "job failed for " << hash_reader_->extension_id() << " "
203             << hash_reader_->relative_path().MaybeAsASCII()
204             << " reason:" << reason;
205     failure_callback_.Run(reason);
206     failure_callback_.Reset();
207   }
208   if (g_test_observer)
209     g_test_observer->JobFinished(
210         hash_reader_->extension_id(), hash_reader_->relative_path(), failed_);
211 }
212 
213 }  // namespace extensions
214