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