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_; |
| 80 |
| 81 DISALLOW_COPY_AND_ASSIGN(ContentHashFetcherWaiter); |
| 82 }; |
| 83 |
| 84 // Used in setting up the behavior of our ContentHashFetcher. |
| 85 class MockDelegate : public ContentVerifierDelegate { |
| 86 public: |
| 87 ContentVerifierDelegate::Mode ShouldBeVerified( |
| 88 const Extension& extension) override { |
| 89 return ContentVerifierDelegate::ENFORCE_STRICT; |
| 90 } |
| 91 |
| 92 ContentVerifierKey GetPublicKey() override { |
| 93 return ContentVerifierKey(kWebstoreSignaturesPublicKey, |
| 94 kWebstoreSignaturesPublicKeySize); |
| 95 } |
| 96 |
| 97 GURL GetSignatureFetchUrl(const std::string& extension_id, |
| 98 const base::Version& version) override { |
| 99 std::string url = |
| 100 base::StringPrintf("http://localhost/getsignature?id=%s&version=%s", |
| 101 extension_id.c_str(), version.GetString().c_str()); |
| 102 return GURL(url); |
| 103 } |
| 104 |
| 105 std::set<base::FilePath> GetBrowserImagePaths( |
| 106 const extensions::Extension* extension) override { |
| 107 ADD_FAILURE() << "Unexpected call for this test"; |
| 108 return std::set<base::FilePath>(); |
| 109 } |
| 110 |
| 111 void VerifyFailed(const std::string& extension_id, |
| 112 ContentVerifyJob::FailureReason reason) override { |
| 113 ADD_FAILURE() << "Unexpected call for this test"; |
| 114 } |
| 115 }; |
| 116 |
| 117 class ContentHashFetcherTest : public ExtensionsTest { |
| 118 public: |
| 119 ContentHashFetcherTest() {} |
| 120 ~ContentHashFetcherTest() override {} |
| 121 |
| 122 void SetUp() override { |
| 123 ExtensionsTest::SetUp(); |
| 124 // We need a real IO thread to be able to intercept the network request |
| 125 // for the missing verified_contents.json file. |
| 126 browser_threads_.reset(new content::TestBrowserThreadBundle( |
| 127 content::TestBrowserThreadBundle::REAL_IO_THREAD)); |
| 128 request_context_ = new net::TestURLRequestContextGetter( |
| 129 content::BrowserThread::GetTaskRunnerForThread( |
| 130 content::BrowserThread::IO)); |
| 131 } |
| 132 |
| 133 net::URLRequestContextGetter* request_context() { |
| 134 return request_context_.get(); |
| 135 } |
| 136 |
| 137 // Helper to get files from our subdirectory in the general extensions test |
| 138 // data dir. |
| 139 base::FilePath GetTestPath(const base::FilePath& relative_path) { |
| 140 base::FilePath base_path; |
| 141 EXPECT_TRUE(PathService::Get(extensions::DIR_TEST_DATA, &base_path)); |
| 142 base_path = base_path.AppendASCII("content_hash_fetcher"); |
| 143 return base_path.Append(relative_path); |
| 144 } |
| 145 |
| 146 // Unzips the extension source from |extension_zip| into a temporary |
| 147 // directory and loads it, returning the resuling Extension object. |
| 148 scoped_refptr<Extension> UnzipToTempDirAndLoad( |
| 149 const base::FilePath& extension_zip) { |
| 150 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| 151 base::FilePath destination = temp_dir_.path(); |
| 152 EXPECT_TRUE(zip::Unzip(extension_zip, destination)); |
| 153 |
| 154 std::string error; |
| 155 scoped_refptr<Extension> extension = file_util::LoadExtension( |
| 156 destination, Manifest::INTERNAL, 0 /* flags */, &error); |
| 157 EXPECT_NE(nullptr, extension.get()) << " error:'" << error << "'"; |
| 158 return extension; |
| 159 } |
| 160 |
| 161 // Registers interception of requests for |url| to respond with the contents |
| 162 // of the file at |response_path|. |
| 163 void RegisterInterception(const GURL& url, |
| 164 const base::FilePath& response_path) { |
| 165 interceptor_ = base::MakeUnique<net::TestURLRequestInterceptor>( |
| 166 url.scheme(), url.host(), |
| 167 content::BrowserThread::GetTaskRunnerForThread( |
| 168 content::BrowserThread::IO), |
| 169 content::BrowserThread::GetBlockingPool()); |
| 170 interceptor_->SetResponse(url, response_path); |
| 171 } |
| 172 |
| 173 protected: |
| 174 std::unique_ptr<content::TestBrowserThreadBundle> browser_threads_; |
| 175 std::unique_ptr<net::TestURLRequestInterceptor> interceptor_; |
| 176 scoped_refptr<net::TestURLRequestContextGetter> request_context_; |
| 177 base::ScopedTempDir temp_dir_; |
| 178 }; |
| 179 |
| 180 // This tests our ability to successfully fetch, parse, and validate a missing |
| 181 // verified_contents.json file for an extension. |
| 182 TEST_F(ContentHashFetcherTest, MissingVerifiedContents) { |
| 183 // We unzip the extension source to a temp directory to simulate it being |
| 184 // installed there, because the ContentHashFetcher will create the _metadata/ |
| 185 // directory within the extension install dir and write the fetched |
| 186 // verified_contents.json file there. |
| 187 base::FilePath test_dir_base = GetTestPath( |
| 188 base::FilePath(FILE_PATH_LITERAL("missing_verified_contents"))); |
| 189 scoped_refptr<Extension> extension = |
| 190 UnzipToTempDirAndLoad(test_dir_base.AppendASCII("source.zip")); |
| 191 |
| 192 // Make sure there isn't already a verified_contents.json file there. |
| 193 EXPECT_FALSE( |
| 194 base::PathExists(file_util::GetVerifiedContentsPath(extension->path()))); |
| 195 |
| 196 MockDelegate delegate; |
| 197 ContentHashFetcherWaiter waiter; |
| 198 GURL fetch_url = |
| 199 delegate.GetSignatureFetchUrl(extension->id(), *extension->version()); |
| 200 |
| 201 RegisterInterception(fetch_url, |
| 202 test_dir_base.AppendASCII("verified_contents.json")); |
| 203 |
| 204 ContentHashFetcher fetcher(request_context(), &delegate, |
| 205 waiter.GetCallback()); |
| 206 fetcher.DoFetch(extension.get(), true /* force */); |
| 207 |
| 208 // Make sure the fetch was successful. |
| 209 std::unique_ptr<ContentHashFetcherResult> result = waiter.WaitForCallback(); |
| 210 ASSERT_TRUE(result.get()); |
| 211 EXPECT_TRUE(result->success); |
| 212 EXPECT_TRUE(result->force); |
| 213 EXPECT_TRUE(result->mismatch_paths.empty()); |
| 214 |
| 215 // Make sure the verified_contents.json file was written into the extension's |
| 216 // install dir. |
| 217 EXPECT_TRUE( |
| 218 base::PathExists(file_util::GetVerifiedContentsPath(extension->path()))); |
| 219 } |
| 220 |
| 221 // Similar to MissingVerifiedContents, but tests the case where the extension |
| 222 // actually has corruption. |
| 223 TEST_F(ContentHashFetcherTest, MissingVerifiedContentsAndCorrupt) { |
| 224 base::FilePath test_dir_base = |
| 225 GetTestPath(base::FilePath()).AppendASCII("missing_verified_contents"); |
| 226 scoped_refptr<Extension> extension = |
| 227 UnzipToTempDirAndLoad(test_dir_base.AppendASCII("source.zip")); |
| 228 |
| 229 // Tamper with a file in the extension. |
| 230 base::FilePath script_path = extension->path().AppendASCII("script.js"); |
| 231 std::string addition = "//hello world"; |
| 232 ASSERT_TRUE( |
| 233 base::AppendToFile(script_path, addition.c_str(), addition.size())); |
| 234 MockDelegate delegate; |
| 235 ContentHashFetcherWaiter waiter; |
| 236 GURL fetch_url = |
| 237 delegate.GetSignatureFetchUrl(extension->id(), *extension->version()); |
| 238 |
| 239 RegisterInterception(fetch_url, |
| 240 test_dir_base.AppendASCII("verified_contents.json")); |
| 241 |
| 242 ContentHashFetcher fetcher(request_context(), &delegate, |
| 243 waiter.GetCallback()); |
| 244 fetcher.DoFetch(extension.get(), true /* force */); |
| 245 |
| 246 // Make sure the fetch was *not* successful. |
| 247 std::unique_ptr<ContentHashFetcherResult> result = waiter.WaitForCallback(); |
| 248 ASSERT_NE(nullptr, result.get()); |
| 249 EXPECT_TRUE(result->success); |
| 250 EXPECT_TRUE(result->force); |
| 251 EXPECT_TRUE( |
| 252 base::ContainsKey(result->mismatch_paths, script_path.BaseName())); |
| 253 |
| 254 // Make sure the verified_contents.json file was written into the extension's |
| 255 // install dir. |
| 256 EXPECT_TRUE( |
| 257 base::PathExists(file_util::GetVerifiedContentsPath(extension->path()))); |
| 258 } |
| 259 |
| 260 } // namespace extensions |
OLD | NEW |