Home | History | Annotate | Download | only in browser
      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:
     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 
     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 
     57 ContentVerifyJob::~ContentVerifyJob() {
     58   UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
     59                        time_spent_.InMicroseconds());
     60 }
     61 
     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 
     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 
    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 
    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 
    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
    189 void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) {
    190   g_test_delegate = delegate;
    191 }
    192 
    193 // static
    194 void ContentVerifyJob::SetObserverForTests(TestObserver* observer) {
    195   g_test_observer = observer;
    196 }
    197 
    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