blob: bd9a55eca666550e7f2fe6a55023ae6432e4b553 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/updater/update_service.h"
#include "extensions/common/extension_urls.h"
#include "extensions/shell/test/shell_test.h"
#include "net/base/escape.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
namespace extensions {
namespace {
using FakeResponse = std::pair<std::string, net::HttpStatusCode>;
// TODO(rockot): In general there's enough mock-Omaha-noise that this might be
// better placed into some test library code in //components/update_client.
FakeResponse CreateFakeUpdateResponse(const std::string& id,
size_t crx_length) {
std::string response_text = base::StringPrintf(
"<gupdate xmlns=\"http://www.google.com/update2/response\" "
" protocol=\"2.0\" server=\"prod\">\n"
" <daystart elapsed_days=\"2860\" elapsed_seconds=\"42042\"/>\n"
" <app appid=\"%s\" status=\"ok\">\n"
" <updatecheck codebase=\"%s\" fp=\"0\" hash=\"\" hash_sha256=\"\" "
" size=\"%d\" status=\"ok\" version=\"1\"/>\n"
" </app>\n"
"</gupdate>\n",
id.c_str(),
base::StringPrintf("https://fake-omaha-hostname/%s.crx",
id.c_str()).c_str(),
static_cast<int>(crx_length));
return std::make_pair(response_text, net::HTTP_OK);
}
FakeResponse CreateFakeUpdateNotFoundResponse() {
return std::make_pair(
std::string(
"<gupdate xmlns=\"http://www.google.com/update2/response\" "
" protocol=\"2.0\" server=\"prod\">\n"
" <daystart elapsed_days=\"4242\" elapsed_seconds=\"42042\"/>\n"
" <app appid=\"\" status=\"error-invalidAppId\">\n"
"</gupdate>"),
net::HTTP_OK);
}
bool ExtractKeyValueFromComponent(const std::string& component_str,
const std::string& target_key,
std::string* extracted_value) {
url::Component component(0, static_cast<int>(component_str.length()));
url::Component key, value;
while (url::ExtractQueryKeyValue(component_str.c_str(), &component, &key,
&value)) {
if (target_key == component_str.substr(key.begin, key.len)) {
*extracted_value = component_str.substr(value.begin, value.len);
return true;
}
}
return false;
}
// This extracts an extension ID from an Omaha update query. Queries have the
// form https://foo/bar/update?x=id%3Dabcdefghijklmnopqrstuvwxyzaaaaaa%26...
// This function extracts the 'x' query parameter (e.g. "id%3Dabcdef...."),
// unescapes its value (to become e.g., "id=abcdef...", and then extracts the
// 'id' value from the result (e.g. "abcdef...").
bool ExtractIdFromUpdateQuery(const std::string& query_str, std::string* id) {
std::string data_string;
if (!ExtractKeyValueFromComponent(query_str, "x", &data_string))
return false;
data_string = net::UnescapeURLComponent(data_string,
net::UnescapeRule::URL_SPECIAL_CHARS);
if (!ExtractKeyValueFromComponent(data_string, "id", id))
return false;
EXPECT_EQ(32u, id->size());
return true;
}
void ExpectDownloadSuccess(const base::Closure& continuation, bool success) {
EXPECT_TRUE(success) << "Download failed.";
continuation.Run();
}
class FakeUpdateURLFetcherFactory : public net::URLFetcherFactory {
public:
~FakeUpdateURLFetcherFactory() override {}
void RegisterFakeExtension(const std::string& id,
const std::string& contents) {
CHECK_EQ(32u, id.size());
fake_extensions_.insert(std::make_pair(id, contents));
}
// net::URLFetcherFactory:
scoped_ptr<net::URLFetcher> CreateURLFetcher(
int id,
const GURL& url,
net::URLFetcher::RequestType request_type,
net::URLFetcherDelegate* delegate) override {
if (url.spec().find(extension_urls::GetWebstoreUpdateUrl().spec()) == 0) {
// Handle fake Omaha requests.
return CreateUpdateManifestFetcher(url, delegate);
} else if (url.spec().find("https://fake-omaha-hostname") == 0) {
// Handle a fake CRX request.
return CreateCrxFetcher(url, delegate);
}
NOTREACHED();
return nullptr;
}
private:
scoped_ptr<net::URLFetcher> CreateUpdateManifestFetcher(
const GURL& url,
net::URLFetcherDelegate* delegate) {
// If we have a fake CRX for the ID, return a fake update blob for it.
// Otherwise return an invalid-ID response.
FakeResponse response;
std::string extension_id;
if (!ExtractIdFromUpdateQuery(url.query(), &extension_id)) {
response = CreateFakeUpdateNotFoundResponse();
} else {
const auto& iter = fake_extensions_.find(extension_id);
if (iter == fake_extensions_.end())
response = CreateFakeUpdateNotFoundResponse();
else
response = CreateFakeUpdateResponse(extension_id, iter->second.size());
}
return scoped_ptr<net::URLFetcher>(
new net::FakeURLFetcher(url, delegate, response.first, response.second,
net::URLRequestStatus::SUCCESS));
}
scoped_ptr<net::URLFetcher> CreateCrxFetcher(
const GURL& url,
net::URLFetcherDelegate* delegate) {
FakeResponse response;
std::string extension_id = url.path().substr(1, 32);
const auto& iter = fake_extensions_.find(extension_id);
if (iter == fake_extensions_.end())
response = std::make_pair(std::string(), net::HTTP_NOT_FOUND);
else
response = std::make_pair(iter->second, net::HTTP_OK);
net::TestURLFetcher* fetcher =
new net::FakeURLFetcher(url, delegate, response.first, response.second,
net::URLRequestStatus::SUCCESS);
fetcher->SetResponseFilePath(base::FilePath::FromUTF8Unsafe(url.path()));
return scoped_ptr<net::URLFetcher>(fetcher);
}
std::map<std::string, std::string> fake_extensions_;
};
} // namespace
class UpdateServiceTest : public AppShellTest {
public:
UpdateServiceTest() {}
~UpdateServiceTest() override {}
void SetUpOnMainThread() override {
AppShellTest::SetUpOnMainThread();
update_service_ = UpdateService::Get(browser_context());
default_url_fetcher_factory_.reset(new FakeUpdateURLFetcherFactory());
url_fetcher_factory_.reset(
new net::FakeURLFetcherFactory(default_url_fetcher_factory_.get()));
}
protected:
void RegisterFakeExtension(const std::string& id,
const std::string& crx_contents) {
default_url_fetcher_factory_->RegisterFakeExtension(id, crx_contents);
}
UpdateService* update_service() const { return update_service_; }
private:
scoped_ptr<FakeUpdateURLFetcherFactory> default_url_fetcher_factory_;
scoped_ptr<net::FakeURLFetcherFactory> url_fetcher_factory_;
UpdateService* update_service_;
};
IN_PROC_BROWSER_TEST_F(UpdateServiceTest, DownloadAndInstall) {
const char kCrxId[] = "abcdefghijklmnopqrstuvwxyzaaaaaa";
const char kCrxContents[] = "Hello, world!";
RegisterFakeExtension(kCrxId, kCrxContents);
base::RunLoop run_loop;
update_service()->DownloadAndInstall(
kCrxId, base::Bind(ExpectDownloadSuccess, run_loop.QuitClosure()));
run_loop.Run();
}
} // namespace extensions