Chromium Code Reviews| 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 |