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