OLD | NEW |
| (Empty) |
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 <map> | |
6 #include <string> | |
7 #include <utility> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/files/scoped_temp_dir.h" | |
11 #include "base/run_loop.h" | |
12 #include "base/strings/stringprintf.h" | |
13 #include "content/public/test/test_browser_thread_bundle.h" | |
14 #include "content/public/test/test_utils.h" | |
15 #include "extensions/browser/api/extensions_api_client.h" | |
16 #include "extensions/browser/extensions_browser_client.h" | |
17 #include "extensions/browser/updater/update_service.h" | |
18 #include "extensions/common/extension_urls.h" | |
19 #include "extensions/shell/test/shell_test.h" | |
20 #include "net/base/escape.h" | |
21 #include "net/http/http_status_code.h" | |
22 #include "net/url_request/test_url_fetcher_factory.h" | |
23 | |
24 namespace extensions { | |
25 | |
26 namespace { | |
27 | |
28 using FakeResponse = std::pair<std::string, net::HttpStatusCode>; | |
29 | |
30 // TODO(rockot): In general there's enough mock-Omaha-noise that this might be | |
31 // better placed into some test library code in //components/update_client. | |
32 FakeResponse CreateFakeUpdateResponse(const std::string& id, | |
33 size_t crx_length) { | |
34 std::string response_text = base::StringPrintf( | |
35 "<gupdate xmlns=\"http://www.google.com/update2/response\" " | |
36 " protocol=\"2.0\" server=\"prod\">\n" | |
37 " <daystart elapsed_days=\"2860\" elapsed_seconds=\"42042\"/>\n" | |
38 " <app appid=\"%s\" status=\"ok\">\n" | |
39 " <updatecheck codebase=\"%s\" fp=\"0\" hash=\"\" hash_sha256=\"\" " | |
40 " size=\"%d\" status=\"ok\" version=\"1\"/>\n" | |
41 " </app>\n" | |
42 "</gupdate>\n", | |
43 id.c_str(), | |
44 base::StringPrintf("https://fake-omaha-hostname/%s.crx", | |
45 id.c_str()).c_str(), | |
46 static_cast<int>(crx_length)); | |
47 return std::make_pair(response_text, net::HTTP_OK); | |
48 } | |
49 | |
50 FakeResponse CreateFakeUpdateNotFoundResponse() { | |
51 return std::make_pair( | |
52 std::string( | |
53 "<gupdate xmlns=\"http://www.google.com/update2/response\" " | |
54 " protocol=\"2.0\" server=\"prod\">\n" | |
55 " <daystart elapsed_days=\"4242\" elapsed_seconds=\"42042\"/>\n" | |
56 " <app appid=\"\" status=\"error-invalidAppId\">\n" | |
57 "</gupdate>"), | |
58 net::HTTP_OK); | |
59 } | |
60 | |
61 bool ExtractKeyValueFromComponent(const std::string& component_str, | |
62 const std::string& target_key, | |
63 std::string* extracted_value) { | |
64 url::Component component(0, static_cast<int>(component_str.length())); | |
65 url::Component key, value; | |
66 while (url::ExtractQueryKeyValue(component_str.c_str(), &component, &key, | |
67 &value)) { | |
68 if (target_key == component_str.substr(key.begin, key.len)) { | |
69 *extracted_value = component_str.substr(value.begin, value.len); | |
70 return true; | |
71 } | |
72 } | |
73 return false; | |
74 } | |
75 | |
76 // This extracts an extension ID from an Omaha update query. Queries have the | |
77 // form https://foo/bar/update?x=id%3Dabcdefghijklmnopqrstuvwxyzaaaaaa%26... | |
78 // This function extracts the 'x' query parameter (e.g. "id%3Dabcdef...."), | |
79 // unescapes its value (to become e.g., "id=abcdef...", and then extracts the | |
80 // 'id' value from the result (e.g. "abcdef..."). | |
81 bool ExtractIdFromUpdateQuery(const std::string& query_str, std::string* id) { | |
82 std::string data_string; | |
83 if (!ExtractKeyValueFromComponent(query_str, "x", &data_string)) | |
84 return false; | |
85 data_string = net::UnescapeURLComponent(data_string, | |
86 net::UnescapeRule::URL_SPECIAL_CHARS); | |
87 if (!ExtractKeyValueFromComponent(data_string, "id", id)) | |
88 return false; | |
89 EXPECT_EQ(32u, id->size()); | |
90 return true; | |
91 } | |
92 | |
93 void ExpectDownloadSuccess(const base::Closure& continuation, bool success) { | |
94 EXPECT_TRUE(success) << "Download failed."; | |
95 continuation.Run(); | |
96 } | |
97 | |
98 class FakeUpdateURLFetcherFactory : public net::URLFetcherFactory { | |
99 public: | |
100 FakeUpdateURLFetcherFactory() { EXPECT_TRUE(dir_.CreateUniqueTempDir()); } | |
101 | |
102 ~FakeUpdateURLFetcherFactory() override {} | |
103 | |
104 void RegisterFakeExtension(const std::string& id, | |
105 const std::string& contents) { | |
106 CHECK_EQ(32u, id.size()); | |
107 fake_extensions_.insert(std::make_pair(id, contents)); | |
108 } | |
109 | |
110 // net::URLFetcherFactory: | |
111 scoped_ptr<net::URLFetcher> CreateURLFetcher( | |
112 int id, | |
113 const GURL& url, | |
114 net::URLFetcher::RequestType request_type, | |
115 net::URLFetcherDelegate* delegate) override { | |
116 if (url.spec().find(extension_urls::GetWebstoreUpdateUrl().spec()) == 0) { | |
117 // Handle fake Omaha requests. | |
118 return CreateUpdateManifestFetcher(url, delegate); | |
119 } else if (url.spec().find("https://fake-omaha-hostname") == 0) { | |
120 // Handle a fake CRX request. | |
121 return CreateCrxFetcher(url, delegate); | |
122 } | |
123 NOTREACHED(); | |
124 return nullptr; | |
125 } | |
126 | |
127 private: | |
128 scoped_ptr<net::URLFetcher> CreateUpdateManifestFetcher( | |
129 const GURL& url, | |
130 net::URLFetcherDelegate* delegate) { | |
131 // If we have a fake CRX for the ID, return a fake update blob for it. | |
132 // Otherwise return an invalid-ID response. | |
133 FakeResponse response; | |
134 std::string extension_id; | |
135 if (!ExtractIdFromUpdateQuery(url.query(), &extension_id)) { | |
136 response = CreateFakeUpdateNotFoundResponse(); | |
137 } else { | |
138 const auto& iter = fake_extensions_.find(extension_id); | |
139 if (iter == fake_extensions_.end()) | |
140 response = CreateFakeUpdateNotFoundResponse(); | |
141 else | |
142 response = CreateFakeUpdateResponse(extension_id, iter->second.size()); | |
143 } | |
144 return scoped_ptr<net::URLFetcher>( | |
145 new net::FakeURLFetcher(url, delegate, response.first, response.second, | |
146 net::URLRequestStatus::SUCCESS)); | |
147 } | |
148 | |
149 scoped_ptr<net::URLFetcher> CreateCrxFetcher( | |
150 const GURL& url, | |
151 net::URLFetcherDelegate* delegate) { | |
152 FakeResponse response; | |
153 std::string extension_id = url.path().substr(1, 32); | |
154 const auto& iter = fake_extensions_.find(extension_id); | |
155 if (iter == fake_extensions_.end()) | |
156 response = std::make_pair(std::string(), net::HTTP_NOT_FOUND); | |
157 else | |
158 response = std::make_pair(iter->second, net::HTTP_OK); | |
159 net::TestURLFetcher* fetcher = | |
160 new net::FakeURLFetcher(url, delegate, response.first, response.second, | |
161 net::URLRequestStatus::SUCCESS); | |
162 base::FilePath path = dir_.path().Append( | |
163 base::FilePath::FromUTF8Unsafe(url.path().substr(1))); | |
164 fetcher->SetResponseFilePath(path); | |
165 return scoped_ptr<net::URLFetcher>(fetcher); | |
166 } | |
167 | |
168 base::ScopedTempDir dir_; | |
169 | |
170 std::map<std::string, std::string> fake_extensions_; | |
171 | |
172 DISALLOW_COPY_AND_ASSIGN(FakeUpdateURLFetcherFactory); | |
173 }; | |
174 | |
175 } // namespace | |
176 | |
177 class UpdateServiceTest : public AppShellTest { | |
178 public: | |
179 UpdateServiceTest() {} | |
180 ~UpdateServiceTest() override {} | |
181 | |
182 void SetUpOnMainThread() override { | |
183 AppShellTest::SetUpOnMainThread(); | |
184 | |
185 update_service_ = UpdateService::Get(browser_context()); | |
186 | |
187 default_url_fetcher_factory_.reset(new FakeUpdateURLFetcherFactory()); | |
188 url_fetcher_factory_.reset( | |
189 new net::FakeURLFetcherFactory(default_url_fetcher_factory_.get())); | |
190 } | |
191 | |
192 protected: | |
193 void RegisterFakeExtension(const std::string& id, | |
194 const std::string& crx_contents) { | |
195 default_url_fetcher_factory_->RegisterFakeExtension(id, crx_contents); | |
196 } | |
197 | |
198 UpdateService* update_service() const { return update_service_; } | |
199 | |
200 private: | |
201 scoped_ptr<FakeUpdateURLFetcherFactory> default_url_fetcher_factory_; | |
202 scoped_ptr<net::FakeURLFetcherFactory> url_fetcher_factory_; | |
203 | |
204 UpdateService* update_service_; | |
205 }; | |
206 | |
207 IN_PROC_BROWSER_TEST_F(UpdateServiceTest, DownloadAndInstall) { | |
208 const char kCrxId[] = "abcdefghijklmnopqrstuvwxyzaaaaaa"; | |
209 const char kCrxContents[] = "Hello, world!"; | |
210 RegisterFakeExtension(kCrxId, kCrxContents); | |
211 | |
212 base::RunLoop run_loop; | |
213 update_service()->DownloadAndInstall( | |
214 kCrxId, base::Bind(ExpectDownloadSuccess, run_loop.QuitClosure())); | |
215 run_loop.Run(); | |
216 } | |
217 | |
218 } // namespace extensions | |
OLD | NEW |