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 18namespace extensions { 19 20namespace { 21 22ContentVerifyJob::TestDelegate* g_test_delegate = NULL; 23ContentVerifyJob::TestObserver* g_test_observer = NULL; 24 25class ScopedElapsedTimer { 26 public: 27 ScopedElapsedTimer(base::TimeDelta* total) : total_(total) { DCHECK(total_); } 28 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 42ContentVerifyJob::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 57ContentVerifyJob::~ContentVerifyJob() { 58 UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS", 59 time_spent_.InMicroseconds()); 60} 61 62void 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 74void 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 121void 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 143bool 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 161void 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 189void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) { 190 g_test_delegate = delegate; 191} 192 193// static 194void ContentVerifyJob::SetObserverForTests(TestObserver* observer) { 195 g_test_observer = observer; 196} 197 198void 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