// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/browser/content_verify_job.h" #include #include "base/metrics/histogram.h" #include "base/stl_util.h" #include "base/task_runner_util.h" #include "base/timer/elapsed_timer.h" #include "content/public/browser/browser_thread.h" #include "crypto/secure_hash.h" #include "crypto/sha2.h" #include "extensions/browser/content_hash_reader.h" namespace extensions { namespace { ContentVerifyJob::TestDelegate* g_test_delegate = NULL; ContentVerifyJob::TestObserver* g_test_observer = NULL; class ScopedElapsedTimer { public: explicit ScopedElapsedTimer(base::TimeDelta* total) : total_(total) { DCHECK(total_); } ~ScopedElapsedTimer() { *total_ += timer.Elapsed(); } private: // Some total amount of time we should add our elapsed time to at // destruction. base::TimeDelta* total_; // A timer for how long this object has been alive. base::ElapsedTimer timer; }; } // namespace ContentVerifyJob::ContentVerifyJob(ContentHashReader* hash_reader, const FailureCallback& failure_callback) : done_reading_(false), hashes_ready_(false), total_bytes_read_(0), current_block_(0), current_hash_byte_count_(0), hash_reader_(hash_reader), failure_callback_(failure_callback), failed_(false) { // It's ok for this object to be constructed on a different thread from where // it's used. thread_checker_.DetachFromThread(); } ContentVerifyJob::~ContentVerifyJob() { UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS", time_spent_.InMicroseconds()); } void ContentVerifyJob::Start() { DCHECK(thread_checker_.CalledOnValidThread()); if (g_test_observer) g_test_observer->JobStarted(hash_reader_->extension_id(), hash_reader_->relative_path()); base::PostTaskAndReplyWithResult( content::BrowserThread::GetBlockingPool(), FROM_HERE, base::Bind(&ContentHashReader::Init, hash_reader_), base::Bind(&ContentVerifyJob::OnHashesReady, this)); } void ContentVerifyJob::BytesRead(int count, const char* data) { ScopedElapsedTimer timer(&time_spent_); DCHECK(thread_checker_.CalledOnValidThread()); if (failed_) return; if (g_test_delegate) { FailureReason reason = g_test_delegate->BytesRead(hash_reader_->extension_id(), count, data); if (reason != NONE) DispatchFailureCallback(reason); return; } if (!hashes_ready_) { queue_.append(data, count); return; } DCHECK_GE(count, 0); int bytes_added = 0; while (bytes_added < count) { if (current_block_ >= hash_reader_->block_count()) return DispatchFailureCallback(HASH_MISMATCH); if (!current_hash_.get()) { current_hash_byte_count_ = 0; current_hash_.reset( crypto::SecureHash::Create(crypto::SecureHash::SHA256)); } // Compute how many bytes we should hash, and add them to the current hash. int bytes_to_hash = std::min(hash_reader_->block_size() - current_hash_byte_count_, count - bytes_added); DCHECK_GT(bytes_to_hash, 0); current_hash_->Update(data + bytes_added, bytes_to_hash); bytes_added += bytes_to_hash; current_hash_byte_count_ += bytes_to_hash; total_bytes_read_ += bytes_to_hash; // If we finished reading a block worth of data, finish computing the hash // for it and make sure the expected hash matches. if (current_hash_byte_count_ == hash_reader_->block_size() && !FinishBlock()) { DispatchFailureCallback(HASH_MISMATCH); return; } } } void ContentVerifyJob::DoneReading() { ScopedElapsedTimer timer(&time_spent_); DCHECK(thread_checker_.CalledOnValidThread()); if (failed_) return; if (g_test_delegate) { FailureReason reason = g_test_delegate->DoneReading(hash_reader_->extension_id()); if (reason != NONE) DispatchFailureCallback(reason); return; } done_reading_ = true; if (hashes_ready_) { if (!FinishBlock()) DispatchFailureCallback(HASH_MISMATCH); else if (g_test_observer) g_test_observer->JobFinished(hash_reader_->extension_id(), hash_reader_->relative_path(), failed_); } } bool ContentVerifyJob::FinishBlock() { if (current_hash_byte_count_ <= 0) return true; std::string final(crypto::kSHA256Length, 0); current_hash_->Finish(string_as_array(&final), final.size()); current_hash_.reset(); current_hash_byte_count_ = 0; int block = current_block_++; const std::string* expected_hash = NULL; if (!hash_reader_->GetHashForBlock(block, &expected_hash) || *expected_hash != final) return false; return true; } void ContentVerifyJob::OnHashesReady(bool success) { if (!success && !g_test_delegate) { if (!hash_reader_->content_exists()) { // Ignore verification of non-existent resources. return; } else if (hash_reader_->have_verified_contents() && hash_reader_->have_computed_hashes()) { DispatchFailureCallback(NO_HASHES_FOR_FILE); } else { DispatchFailureCallback(MISSING_ALL_HASHES); } return; } hashes_ready_ = true; if (!queue_.empty()) { std::string tmp; queue_.swap(tmp); BytesRead(tmp.size(), string_as_array(&tmp)); } if (done_reading_) { ScopedElapsedTimer timer(&time_spent_); if (!FinishBlock()) { DispatchFailureCallback(HASH_MISMATCH); } else if (g_test_observer) { g_test_observer->JobFinished(hash_reader_->extension_id(), hash_reader_->relative_path(), failed_); } } } // static void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) { g_test_delegate = delegate; } // static void ContentVerifyJob::SetObserverForTests(TestObserver* observer) { g_test_observer = observer; } void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) { DCHECK(!failed_); failed_ = true; if (!failure_callback_.is_null()) { VLOG(1) << "job failed for " << hash_reader_->extension_id() << " " << hash_reader_->relative_path().MaybeAsASCII() << " reason:" << reason; failure_callback_.Run(reason); failure_callback_.Reset(); } if (g_test_observer) g_test_observer->JobFinished( hash_reader_->extension_id(), hash_reader_->relative_path(), failed_); } } // namespace extensions