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.
10 #include "base/run_loop.h"
11 #include "base/strings/stringprintf.h"
12 #include "content/public/test/test_browser_thread_bundle.h"
13 #include "content/public/test/test_utils.h"
14 #include "extensions/browser/api/extensions_api_client.h"
15 #include "extensions/browser/extensions_browser_client.h"
16 #include "extensions/browser/updater/update_service.h"
17 #include "extensions/common/extension_urls.h"
18 #include "extensions/shell/test/shell_test.h"
19 #include "net/base/escape.h"
20 #include "net/http/http_status_code.h"
21 #include "net/url_request/test_url_fetcher_factory.h"
23 namespace extensions
{
27 using FakeResponse
= std::pair
<std::string
, net::HttpStatusCode
>;
29 // TODO(rockot): In general there's enough mock-Omaha-noise that this might be
30 // better placed into some test library code in //components/update_client.
31 FakeResponse
CreateFakeUpdateResponse(const std::string
& id
,
33 std::string response_text
= base::StringPrintf(
34 "<gupdate xmlns=\"http://www.google.com/update2/response\" "
35 " protocol=\"2.0\" server=\"prod\">\n"
36 " <daystart elapsed_days=\"2860\" elapsed_seconds=\"42042\"/>\n"
37 " <app appid=\"%s\" status=\"ok\">\n"
38 " <updatecheck codebase=\"%s\" fp=\"0\" hash=\"\" hash_sha256=\"\" "
39 " size=\"%d\" status=\"ok\" version=\"1\"/>\n"
43 base::StringPrintf("https://fake-omaha-hostname/%s.crx",
45 static_cast<int>(crx_length
));
46 return std::make_pair(response_text
, net::HTTP_OK
);
49 FakeResponse
CreateFakeUpdateNotFoundResponse() {
50 return std::make_pair(
52 "<gupdate xmlns=\"http://www.google.com/update2/response\" "
53 " protocol=\"2.0\" server=\"prod\">\n"
54 " <daystart elapsed_days=\"4242\" elapsed_seconds=\"42042\"/>\n"
55 " <app appid=\"\" status=\"error-invalidAppId\">\n"
60 bool ExtractKeyValueFromComponent(const std::string
& component_str
,
61 const std::string
& target_key
,
62 std::string
* extracted_value
) {
63 url::Component
component(0, static_cast<int>(component_str
.length()));
64 url::Component key
, value
;
65 while (url::ExtractQueryKeyValue(component_str
.c_str(), &component
, &key
,
67 if (target_key
== component_str
.substr(key
.begin
, key
.len
)) {
68 *extracted_value
= component_str
.substr(value
.begin
, value
.len
);
75 // This extracts an extension ID from an Omaha update query. Queries have the
76 // form https://foo/bar/update?x=id%3Dabcdefghijklmnopqrstuvwxyzaaaaaa%26...
77 // This function extracts the 'x' query parameter (e.g. "id%3Dabcdef...."),
78 // unescapes its value (to become e.g., "id=abcdef...", and then extracts the
79 // 'id' value from the result (e.g. "abcdef...").
80 bool ExtractIdFromUpdateQuery(const std::string
& query_str
, std::string
* id
) {
81 std::string data_string
;
82 if (!ExtractKeyValueFromComponent(query_str
, "x", &data_string
))
84 data_string
= net::UnescapeURLComponent(data_string
,
85 net::UnescapeRule::URL_SPECIAL_CHARS
);
86 if (!ExtractKeyValueFromComponent(data_string
, "id", id
))
88 EXPECT_EQ(32u, id
->size());
92 void ExpectDownloadSuccess(const base::Closure
& continuation
, bool success
) {
93 EXPECT_TRUE(success
) << "Download failed.";
97 class FakeUpdateURLFetcherFactory
: public net::URLFetcherFactory
{
99 ~FakeUpdateURLFetcherFactory() override
{}
101 void RegisterFakeExtension(const std::string
& id
,
102 const std::string
& contents
) {
103 CHECK_EQ(32u, id
.size());
104 fake_extensions_
.insert(std::make_pair(id
, contents
));
107 // net::URLFetcherFactory:
108 scoped_ptr
<net::URLFetcher
> CreateURLFetcher(
111 net::URLFetcher::RequestType request_type
,
112 net::URLFetcherDelegate
* delegate
) override
{
113 if (url
.spec().find(extension_urls::GetWebstoreUpdateUrl().spec()) == 0) {
114 // Handle fake Omaha requests.
115 return CreateUpdateManifestFetcher(url
, delegate
);
116 } else if (url
.spec().find("https://fake-omaha-hostname") == 0) {
117 // Handle a fake CRX request.
118 return CreateCrxFetcher(url
, delegate
);
125 scoped_ptr
<net::URLFetcher
> CreateUpdateManifestFetcher(
127 net::URLFetcherDelegate
* delegate
) {
128 // If we have a fake CRX for the ID, return a fake update blob for it.
129 // Otherwise return an invalid-ID response.
130 FakeResponse response
;
131 std::string extension_id
;
132 if (!ExtractIdFromUpdateQuery(url
.query(), &extension_id
)) {
133 response
= CreateFakeUpdateNotFoundResponse();
135 const auto& iter
= fake_extensions_
.find(extension_id
);
136 if (iter
== fake_extensions_
.end())
137 response
= CreateFakeUpdateNotFoundResponse();
139 response
= CreateFakeUpdateResponse(extension_id
, iter
->second
.size());
141 return scoped_ptr
<net::URLFetcher
>(
142 new net::FakeURLFetcher(url
, delegate
, response
.first
, response
.second
,
143 net::URLRequestStatus::SUCCESS
));
146 scoped_ptr
<net::URLFetcher
> CreateCrxFetcher(
148 net::URLFetcherDelegate
* delegate
) {
149 FakeResponse response
;
150 std::string extension_id
= url
.path().substr(1, 32);
151 const auto& iter
= fake_extensions_
.find(extension_id
);
152 if (iter
== fake_extensions_
.end())
153 response
= std::make_pair(std::string(), net::HTTP_NOT_FOUND
);
155 response
= std::make_pair(iter
->second
, net::HTTP_OK
);
156 net::TestURLFetcher
* fetcher
=
157 new net::FakeURLFetcher(url
, delegate
, response
.first
, response
.second
,
158 net::URLRequestStatus::SUCCESS
);
159 fetcher
->SetResponseFilePath(base::FilePath::FromUTF8Unsafe(url
.path()));
160 return scoped_ptr
<net::URLFetcher
>(fetcher
);
163 std::map
<std::string
, std::string
> fake_extensions_
;
168 class UpdateServiceTest
: public AppShellTest
{
170 UpdateServiceTest() {}
171 ~UpdateServiceTest() override
{}
173 void SetUpOnMainThread() override
{
174 AppShellTest::SetUpOnMainThread();
176 update_service_
= UpdateService::Get(browser_context());
178 default_url_fetcher_factory_
.reset(new FakeUpdateURLFetcherFactory());
179 url_fetcher_factory_
.reset(
180 new net::FakeURLFetcherFactory(default_url_fetcher_factory_
.get()));
184 void RegisterFakeExtension(const std::string
& id
,
185 const std::string
& crx_contents
) {
186 default_url_fetcher_factory_
->RegisterFakeExtension(id
, crx_contents
);
189 UpdateService
* update_service() const { return update_service_
; }
192 scoped_ptr
<FakeUpdateURLFetcherFactory
> default_url_fetcher_factory_
;
193 scoped_ptr
<net::FakeURLFetcherFactory
> url_fetcher_factory_
;
195 UpdateService
* update_service_
;
198 IN_PROC_BROWSER_TEST_F(UpdateServiceTest
, DownloadAndInstall
) {
199 const char kCrxId
[] = "abcdefghijklmnopqrstuvwxyzaaaaaa";
200 const char kCrxContents
[] = "Hello, world!";
201 RegisterFakeExtension(kCrxId
, kCrxContents
);
203 base::RunLoop run_loop
;
204 update_service()->DownloadAndInstall(
205 kCrxId
, base::Bind(ExpectDownloadSuccess
, run_loop
.QuitClosure()));
209 } // namespace extensions