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.
5 #include "extensions/browser/computed_hashes.h"
7 #include "base/base64.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/json/json_reader.h"
11 #include "base/json/json_writer.h"
12 #include "base/stl_util.h"
13 #include "base/values.h"
14 #include "crypto/secure_hash.h"
15 #include "crypto/sha2.h"
18 const char kBlockHashesKey
[] = "block_hashes";
19 const char kBlockSizeKey
[] = "block_size";
20 const char kFileHashesKey
[] = "file_hashes";
21 const char kPathKey
[] = "path";
22 const char kVersionKey
[] = "version";
23 const int kVersion
= 2;
26 namespace extensions
{
28 ComputedHashes::Reader::Reader() {
31 ComputedHashes::Reader::~Reader() {
34 bool ComputedHashes::Reader::InitFromFile(const base::FilePath
& path
) {
36 if (!base::ReadFileToString(path
, &contents
))
39 base::DictionaryValue
* top_dictionary
= NULL
;
40 scoped_ptr
<base::Value
> value(base::JSONReader::Read(contents
));
41 if (!value
.get() || !value
->GetAsDictionary(&top_dictionary
))
44 // For now we don't support forwards or backwards compatability in the
45 // format, so we return false on version mismatch.
47 if (!top_dictionary
->GetInteger(kVersionKey
, &version
) || version
!= kVersion
)
50 base::ListValue
* all_hashes
= NULL
;
51 if (!top_dictionary
->GetList(kFileHashesKey
, &all_hashes
))
54 for (size_t i
= 0; i
< all_hashes
->GetSize(); i
++) {
55 base::DictionaryValue
* dictionary
= NULL
;
56 if (!all_hashes
->GetDictionary(i
, &dictionary
))
59 std::string relative_path_utf8
;
60 if (!dictionary
->GetString(kPathKey
, &relative_path_utf8
))
64 if (!dictionary
->GetInteger(kBlockSizeKey
, &block_size
))
66 if (block_size
<= 0 || ((block_size
% 1024) != 0)) {
67 LOG(ERROR
) << "Invalid block size: " << block_size
;
72 base::ListValue
* hashes_list
= NULL
;
73 if (!dictionary
->GetList(kBlockHashesKey
, &hashes_list
))
76 base::FilePath relative_path
=
77 base::FilePath::FromUTF8Unsafe(relative_path_utf8
);
78 relative_path
= relative_path
.NormalizePathSeparatorsTo('/');
80 data_
[relative_path
] = HashInfo(block_size
, std::vector
<std::string
>());
81 std::vector
<std::string
>* hashes
= &(data_
[relative_path
].second
);
83 for (size_t j
= 0; j
< hashes_list
->GetSize(); j
++) {
85 if (!hashes_list
->GetString(j
, &encoded
))
88 hashes
->push_back(std::string());
89 std::string
* decoded
= &hashes
->back();
90 if (!base::Base64Decode(encoded
, decoded
)) {
99 bool ComputedHashes::Reader::GetHashes(const base::FilePath
& relative_path
,
101 std::vector
<std::string
>* hashes
) {
102 base::FilePath path
= relative_path
.NormalizePathSeparatorsTo('/');
103 std::map
<base::FilePath
, HashInfo
>::iterator i
= data_
.find(path
);
104 if (i
== data_
.end()) {
105 // If we didn't find the entry using exact match, it's possible the
106 // developer is using a path with some letters in the incorrect case, which
107 // happens to work on windows/osx. So try doing a linear scan to look for a
108 // case-insensitive match. In practice most extensions don't have that big
109 // a list of files so the performance penalty is probably not too big
110 // here. Also for crbug.com/29941 we plan to start warning developers when
111 // they are making this mistake, since their extension will be broken on
113 for (i
= data_
.begin(); i
!= data_
.end(); ++i
) {
114 const base::FilePath
& entry
= i
->first
;
115 if (base::FilePath::CompareEqualIgnoreCase(entry
.value(), path
.value()))
118 if (i
== data_
.end())
121 HashInfo
& info
= i
->second
;
122 *block_size
= info
.first
;
123 *hashes
= info
.second
;
127 ComputedHashes::Writer::Writer() : file_list_(new base::ListValue
) {
130 ComputedHashes::Writer::~Writer() {
133 void ComputedHashes::Writer::AddHashes(const base::FilePath
& relative_path
,
135 const std::vector
<std::string
>& hashes
) {
136 base::DictionaryValue
* dict
= new base::DictionaryValue();
137 base::ListValue
* block_hashes
= new base::ListValue();
138 file_list_
->Append(dict
);
139 dict
->SetString(kPathKey
,
140 relative_path
.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
141 dict
->SetInteger(kBlockSizeKey
, block_size
);
142 dict
->Set(kBlockHashesKey
, block_hashes
);
144 for (std::vector
<std::string
>::const_iterator i
= hashes
.begin();
148 base::Base64Encode(*i
, &encoded
);
149 block_hashes
->AppendString(encoded
);
153 bool ComputedHashes::Writer::WriteToFile(const base::FilePath
& path
) {
155 base::DictionaryValue top_dictionary
;
156 top_dictionary
.SetInteger(kVersionKey
, kVersion
);
157 top_dictionary
.Set(kFileHashesKey
, file_list_
.release());
159 if (!base::JSONWriter::Write(top_dictionary
, &json
))
161 int written
= base::WriteFile(path
, json
.data(), json
.size());
162 if (static_cast<unsigned>(written
) != json
.size()) {
163 LOG(ERROR
) << "Error writing " << path
.AsUTF8Unsafe()
164 << " ; write result:" << written
<< " expected:" << json
.size();
170 void ComputedHashes::ComputeHashesForContent(const std::string
& contents
,
172 std::vector
<std::string
>* hashes
) {
174 // Even when the contents is empty, we want to output at least one hash
175 // block (the hash of the empty string).
177 const char* block_start
= contents
.data() + offset
;
178 DCHECK(offset
<= contents
.size());
179 size_t bytes_to_read
= std::min(contents
.size() - offset
, block_size
);
180 scoped_ptr
<crypto::SecureHash
> hash(
181 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
182 hash
->Update(block_start
, bytes_to_read
);
184 hashes
->push_back(std::string());
185 std::string
* buffer
= &(hashes
->back());
186 buffer
->resize(crypto::kSHA256Length
);
187 hash
->Finish(string_as_array(buffer
), buffer
->size());
189 // If |contents| is empty, then we want to just exit here.
190 if (bytes_to_read
== 0)
193 offset
+= bytes_to_read
;
194 } while (offset
< contents
.size());
197 } // namespace extensions