OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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 <memory> | |
6 | |
7 #include "base/callback.h" | |
8 #include "base/callback_helpers.h" | |
9 #include "base/files/file_path.h" | |
10 #include "base/files/file_util.h" | |
11 #include "base/files/scoped_temp_dir.h" | |
12 #include "base/memory/ptr_util.h" | |
13 #include "base/memory/weak_ptr.h" | |
14 #include "base/path_service.h" | |
15 #include "base/run_loop.h" | |
16 #include "base/strings/stringprintf.h" | |
17 #include "base/version.h" | |
18 #include "content/public/browser/browser_thread.h" | |
19 #include "content/public/test/test_browser_thread_bundle.h" | |
20 #include "extensions/browser/content_hash_fetcher.h" | |
21 #include "extensions/browser/content_verifier_delegate.h" | |
22 #include "extensions/browser/extensions_test.h" | |
23 #include "extensions/common/constants.h" | |
24 #include "extensions/common/extension_paths.h" | |
25 #include "extensions/common/file_util.h" | |
26 #include "net/url_request/test_url_request_interceptor.h" | |
27 #include "net/url_request/url_request_interceptor.h" | |
28 #include "net/url_request/url_request_test_util.h" | |
29 #include "testing/gtest/include/gtest/gtest.h" | |
30 #include "third_party/zlib/google/zip.h" | |
31 | |
32 namespace extensions { | |
33 | |
34 // Used to hold the result of a callback from the ContentHashFetcher. | |
35 struct ContentHashFetcherResult { | |
36 std::string extension_id; | |
37 bool success; | |
38 bool force; | |
39 std::set<base::FilePath> mismatch_paths; | |
40 }; | |
41 | |
42 // Allows waiting for the callback from a ContentHashFetcher, returning the | |
43 // data that was passed to that callback. | |
44 class ContentHashFetcherWaiter { | |
45 public: | |
46 ContentHashFetcherWaiter() : weak_factory_(this) {} | |
47 | |
48 ContentHashFetcher::FetchCallback GetCallback() { | |
49 return base::Bind(&ContentHashFetcherWaiter::Callback, | |
50 weak_factory_.GetWeakPtr()); | |
51 } | |
52 | |
53 std::unique_ptr<ContentHashFetcherResult> WaitForCallback() { | |
54 if (!result_) { | |
55 base::RunLoop run_loop; | |
56 run_loop_quit_ = run_loop.QuitClosure(); | |
57 run_loop.Run(); | |
58 } | |
59 return std::move(result_); | |
60 } | |
61 | |
62 private: | |
63 // Matches signature of ContentHashFetcher::FetchCallback. | |
64 void Callback(const std::string& extension_id, | |
65 bool success, | |
66 bool force, | |
67 const std::set<base::FilePath>& mismatch_paths) { | |
68 result_ = base::MakeUnique<ContentHashFetcherResult>(); | |
69 result_->extension_id = extension_id; | |
70 result_->success = success; | |
71 result_->force = force; | |
72 result_->mismatch_paths = mismatch_paths; | |
73 if (run_loop_quit_) | |
74 base::ResetAndReturn(&run_loop_quit_).Run(); | |
75 } | |
76 | |
77 base::Closure run_loop_quit_; | |
78 std::unique_ptr<ContentHashFetcherResult> result_; | |
79 base::WeakPtrFactory<ContentHashFetcherWaiter> weak_factory_; | |
lazyboy
2016/09/14 22:23:20
DISALLOW_COPY_AND_ASSIGN
asargent_no_longer_on_chrome
2016/09/14 23:26:24
Done.
| |
80 }; | |
81 | |
82 // Used in setting up the behavior of our ContentHashFetcher. | |
83 class MockDelegate : public ContentVerifierDelegate { | |
84 public: | |
85 ContentVerifierDelegate::Mode ShouldBeVerified( | |
86 const Extension& extension) override { | |
87 return ContentVerifierDelegate::ENFORCE_STRICT; | |
88 } | |
89 | |
90 ContentVerifierKey GetPublicKey() override { | |
91 return ContentVerifierKey(kWebstoreSignaturesPublicKey, | |
92 kWebstoreSignaturesPublicKeySize); | |
93 } | |
94 | |
95 GURL GetSignatureFetchUrl(const std::string& extension_id, | |
96 const base::Version& version) override { | |
97 std::string url = | |
98 base::StringPrintf("http://localhost/getsignature?id=%s&version=%s", | |
99 extension_id.c_str(), version.GetString().c_str()); | |
100 return GURL(url); | |
101 } | |
102 | |
103 std::set<base::FilePath> GetBrowserImagePaths( | |
104 const extensions::Extension* extension) override { | |
105 ADD_FAILURE() << "Unexpected call for this test"; | |
106 return std::set<base::FilePath>(); | |
107 } | |
108 | |
109 void VerifyFailed(const std::string& extension_id, | |
110 ContentVerifyJob::FailureReason reason) override { | |
111 ADD_FAILURE() << "Unexpected call for this test"; | |
112 } | |
113 }; | |
114 | |
115 class ContentHashFetcherTest : public ExtensionsTest { | |
116 public: | |
117 ContentHashFetcherTest() {} | |
118 ~ContentHashFetcherTest() override {} | |
119 | |
120 void SetUp() override { | |
121 ExtensionsTest::SetUp(); | |
122 // We need a real IO thread to be able to intercept the network request | |
123 // for the missing verified_contents.json file. | |
124 browser_threads_.reset(new content::TestBrowserThreadBundle( | |
125 content::TestBrowserThreadBundle::REAL_IO_THREAD)); | |
126 request_context_ = new net::TestURLRequestContextGetter( | |
127 content::BrowserThread::GetTaskRunnerForThread( | |
128 content::BrowserThread::IO)); | |
129 } | |
130 | |
131 net::URLRequestContextGetter* request_context() { | |
132 return request_context_.get(); | |
133 } | |
134 | |
135 // Helper to get files from our subdirectory in the general extensions test | |
136 // data dir. | |
137 base::FilePath GetTestPath(const base::FilePath& relative_path) { | |
138 base::FilePath base_path; | |
139 EXPECT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &base_path)); | |
140 base_path = base_path.AppendASCII("content_hash_fetcher"); | |
141 return base_path.Append(relative_path); | |
142 } | |
143 | |
144 // Unzips the extension source from |extension_zip| into a temporary | |
145 // directory and loads it, returning the resuling Extension object. | |
146 scoped_refptr<Extension> UnzipToTempDirAndLoad( | |
147 const base::FilePath& extension_zip) { | |
148 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); | |
149 base::FilePath destination = temp_dir_.path(); | |
150 EXPECT_TRUE(zip::Unzip(extension_zip, destination)); | |
151 | |
152 std::string error; | |
153 scoped_refptr<Extension> extension = file_util::LoadExtension( | |
154 destination, Manifest::INTERNAL, 0 /* flags */, &error); | |
155 EXPECT_NE(nullptr, extension.get()) << " error:'" << error << "'"; | |
156 return extension; | |
157 } | |
158 | |
159 // Registers interception of requests for |url| to respond with the contents | |
160 // of the file at |response_path|. | |
161 void RegisterInterception(const GURL& url, | |
162 const base::FilePath& response_path) { | |
163 interceptor_ = base::MakeUnique<net::TestURLRequestInterceptor>( | |
164 url.scheme(), url.host(), | |
165 content::BrowserThread::GetTaskRunnerForThread( | |
166 content::BrowserThread::IO), | |
167 content::BrowserThread::GetBlockingPool()); | |
168 interceptor_->SetResponse(url, response_path); | |
169 } | |
170 | |
171 protected: | |
172 std::unique_ptr<content::TestBrowserThreadBundle> browser_threads_; | |
173 std::unique_ptr<net::TestURLRequestInterceptor> interceptor_; | |
174 scoped_refptr<net::TestURLRequestContextGetter> request_context_; | |
175 base::ScopedTempDir temp_dir_; | |
176 }; | |
177 | |
178 // This tests our ability to successfully fetch, parse, and validate a missing | |
179 // verified_contents.json file for an extension. | |
180 TEST_F(ContentHashFetcherTest, MissingVerifiedContents) { | |
181 // We unzip the extension source to a temp directory to simulate it being | |
182 // installed there, because the ContentHashFetcher will create the _metadata/ | |
183 // directory within the extension install dir and write the fetched | |
184 // verified_contents.json file there. | |
185 base::FilePath test_dir_base = GetTestPath( | |
186 base::FilePath(FILE_PATH_LITERAL("missing_verified_contents"))); | |
187 scoped_refptr<Extension> extension = | |
188 UnzipToTempDirAndLoad(test_dir_base.AppendASCII("source.zip")); | |
lazyboy
2016/09/14 22:23:20
Make sure there wasn't any verified_contents.json
asargent_no_longer_on_chrome
2016/09/14 23:26:24
Great idea, done.
| |
189 | |
190 MockDelegate delegate; | |
191 ContentHashFetcherWaiter waiter; | |
192 GURL fetch_url = | |
193 delegate.GetSignatureFetchUrl(extension->id(), *extension->version()); | |
194 | |
195 RegisterInterception(fetch_url, | |
196 test_dir_base.AppendASCII("verified_contents.json")); | |
197 | |
198 ContentHashFetcher fetcher(request_context(), &delegate, | |
199 waiter.GetCallback()); | |
200 fetcher.DoFetch(extension.get(), true /* force */); | |
201 | |
202 // Make sure the fetch was successful. | |
203 std::unique_ptr<ContentHashFetcherResult> result = waiter.WaitForCallback(); | |
204 ASSERT_NE(nullptr, result.get()); | |
lazyboy
2016/09/14 22:23:20
ASSERT_TRUE(result.get())?
asargent_no_longer_on_chrome
2016/09/14 23:26:24
Done.
| |
205 EXPECT_TRUE(result->success); | |
206 EXPECT_TRUE(result->force); | |
207 EXPECT_TRUE(result->mismatch_paths.empty()); | |
208 | |
209 // Make sure the verified_contents.json file was written into the extension's | |
210 // install dir. | |
211 EXPECT_TRUE( | |
212 base::PathExists(file_util::GetVerifiedContentsPath(extension->path()))); | |
213 } | |
214 | |
215 // Similar to MissingVerifiedContents, but tests the case where the extension | |
216 // actually has corruption. | |
217 TEST_F(ContentHashFetcherTest, MissingVerifiedContentsAndCorrupt) { | |
218 base::FilePath test_dir_base = | |
219 GetTestPath(base::FilePath()).AppendASCII("missing_verified_contents"); | |
220 scoped_refptr<Extension> extension = | |
221 UnzipToTempDirAndLoad(test_dir_base.AppendASCII("source.zip")); | |
222 | |
223 // Tamper with a file in the extension. | |
224 base::FilePath script_path = extension->path().AppendASCII("script.js"); | |
225 std::string addition = "//hello world"; | |
226 ASSERT_TRUE( | |
227 base::AppendToFile(script_path, addition.c_str(), addition.size())); | |
228 MockDelegate delegate; | |
229 ContentHashFetcherWaiter waiter; | |
230 GURL fetch_url = | |
231 delegate.GetSignatureFetchUrl(extension->id(), *extension->version()); | |
232 | |
233 RegisterInterception(fetch_url, | |
234 test_dir_base.AppendASCII("verified_contents.json")); | |
235 | |
236 ContentHashFetcher fetcher(request_context(), &delegate, | |
237 waiter.GetCallback()); | |
238 fetcher.DoFetch(extension.get(), true /* force */); | |
239 | |
240 // Make sure the fetch was *not* successful. | |
241 std::unique_ptr<ContentHashFetcherResult> result = waiter.WaitForCallback(); | |
242 ASSERT_NE(nullptr, result.get()); | |
243 EXPECT_TRUE(result->success); | |
244 EXPECT_TRUE(result->force); | |
245 EXPECT_TRUE( | |
246 base::ContainsKey(result->mismatch_paths, script_path.BaseName())); | |
247 | |
248 // Make sure the verified_contents.json file was written into the extension's | |
249 // install dir. | |
250 EXPECT_TRUE( | |
251 base::PathExists(file_util::GetVerifiedContentsPath(extension->path()))); | |
252 } | |
253 | |
254 } // namespace extensions | |
OLD | NEW |