1 // Copyright (c) 2012 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 "chrome/browser/extensions/extension_apitest.h"
6
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/browser/extensions/unpacked_installer.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/extensions/application_launch.h"
15 #include "chrome/test/base/ui_test_utils.h"
16 #include "extensions/browser/api/test/test_api.h"
17 #include "extensions/browser/extension_system.h"
18 #include "extensions/common/extension.h"
19 #include "extensions/common/extension_set.h"
20 #include "extensions/test/result_catcher.h"
21 #include "net/base/escape.h"
22 #include "net/base/filename_util.h"
23 #include "net/test/embedded_test_server/embedded_test_server.h"
24 #include "net/test/embedded_test_server/http_request.h"
25 #include "net/test/embedded_test_server/http_response.h"
26 #include "net/test/spawned_test_server/spawned_test_server.h"
27
28 namespace {
29
30 const char kTestCustomArg[] = "customArg";
31 const char kTestServerPort[] = "testServer.port";
32 const char kTestDataDirectory[] = "testDataDirectory";
33 const char kTestWebSocketPort[] = "testWebSocketPort";
34 const char kFtpServerPort[] = "ftpServer.port";
35 const char kSpawnedTestServerPort[] = "spawnedTestServer.port";
36
HandleServerRedirectRequest(const net::test_server::HttpRequest & request)37 scoped_ptr<net::test_server::HttpResponse> HandleServerRedirectRequest(
38 const net::test_server::HttpRequest& request) {
39 if (!StartsWithASCII(request.relative_url, "/server-redirect?", true))
40 return scoped_ptr<net::test_server::HttpResponse>();
41
42 size_t query_string_pos = request.relative_url.find('?');
43 std::string redirect_target =
44 request.relative_url.substr(query_string_pos + 1);
45
46 scoped_ptr<net::test_server::BasicHttpResponse> http_response(
47 new net::test_server::BasicHttpResponse);
48 http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
49 http_response->AddCustomHeader("Location", redirect_target);
50 return http_response.PassAs<net::test_server::HttpResponse>();
51 }
52
HandleEchoHeaderRequest(const net::test_server::HttpRequest & request)53 scoped_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest(
54 const net::test_server::HttpRequest& request) {
55 if (!StartsWithASCII(request.relative_url, "/echoheader?", true))
56 return scoped_ptr<net::test_server::HttpResponse>();
57
58 size_t query_string_pos = request.relative_url.find('?');
59 std::string header_name =
60 request.relative_url.substr(query_string_pos + 1);
61
62 std::string header_value;
63 std::map<std::string, std::string>::const_iterator it = request.headers.find(
64 header_name);
65 if (it != request.headers.end())
66 header_value = it->second;
67
68 scoped_ptr<net::test_server::BasicHttpResponse> http_response(
69 new net::test_server::BasicHttpResponse);
70 http_response->set_code(net::HTTP_OK);
71 http_response->set_content(header_value);
72 return http_response.PassAs<net::test_server::HttpResponse>();
73 }
74
HandleSetCookieRequest(const net::test_server::HttpRequest & request)75 scoped_ptr<net::test_server::HttpResponse> HandleSetCookieRequest(
76 const net::test_server::HttpRequest& request) {
77 if (!StartsWithASCII(request.relative_url, "/set-cookie?", true))
78 return scoped_ptr<net::test_server::HttpResponse>();
79
80 scoped_ptr<net::test_server::BasicHttpResponse> http_response(
81 new net::test_server::BasicHttpResponse);
82 http_response->set_code(net::HTTP_OK);
83
84 size_t query_string_pos = request.relative_url.find('?');
85 std::string cookie_value =
86 request.relative_url.substr(query_string_pos + 1);
87
88 std::vector<std::string> cookies;
89 base::SplitString(cookie_value, '&', &cookies);
90
91 for (size_t i = 0; i < cookies.size(); i++)
92 http_response->AddCustomHeader("Set-Cookie", cookies[i]);
93
94 return http_response.PassAs<net::test_server::HttpResponse>();
95 }
96
HandleSetHeaderRequest(const net::test_server::HttpRequest & request)97 scoped_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest(
98 const net::test_server::HttpRequest& request) {
99 if (!StartsWithASCII(request.relative_url, "/set-header?", true))
100 return scoped_ptr<net::test_server::HttpResponse>();
101
102 size_t query_string_pos = request.relative_url.find('?');
103 std::string escaped_header =
104 request.relative_url.substr(query_string_pos + 1);
105
106 std::string header =
107 net::UnescapeURLComponent(escaped_header,
108 net::UnescapeRule::NORMAL |
109 net::UnescapeRule::SPACES |
110 net::UnescapeRule::URL_SPECIAL_CHARS);
111
112 size_t colon_pos = header.find(':');
113 if (colon_pos == std::string::npos)
114 return scoped_ptr<net::test_server::HttpResponse>();
115
116 std::string header_name = header.substr(0, colon_pos);
117 // Skip space after colon.
118 std::string header_value = header.substr(colon_pos + 2);
119
120 scoped_ptr<net::test_server::BasicHttpResponse> http_response(
121 new net::test_server::BasicHttpResponse);
122 http_response->set_code(net::HTTP_OK);
123 http_response->AddCustomHeader(header_name, header_value);
124 return http_response.PassAs<net::test_server::HttpResponse>();
125 }
126
127 }; // namespace
128
ExtensionApiTest()129 ExtensionApiTest::ExtensionApiTest() {
130 embedded_test_server()->RegisterRequestHandler(
131 base::Bind(&HandleServerRedirectRequest));
132 embedded_test_server()->RegisterRequestHandler(
133 base::Bind(&HandleEchoHeaderRequest));
134 embedded_test_server()->RegisterRequestHandler(
135 base::Bind(&HandleSetCookieRequest));
136 embedded_test_server()->RegisterRequestHandler(
137 base::Bind(&HandleSetHeaderRequest));
138 }
139
~ExtensionApiTest()140 ExtensionApiTest::~ExtensionApiTest() {}
141
SetUpInProcessBrowserTestFixture()142 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
143 DCHECK(!test_config_.get()) << "Previous test did not clear config state.";
144 test_config_.reset(new base::DictionaryValue());
145 test_config_->SetString(kTestDataDirectory,
146 net::FilePathToFileURL(test_data_dir_).spec());
147 test_config_->SetInteger(kTestWebSocketPort, 0);
148 extensions::TestGetConfigFunction::set_test_config_state(
149 test_config_.get());
150 }
151
TearDownInProcessBrowserTestFixture()152 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
153 extensions::TestGetConfigFunction::set_test_config_state(NULL);
154 test_config_.reset(NULL);
155 }
156
RunExtensionTest(const std::string & extension_name)157 bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) {
158 return RunExtensionTestImpl(
159 extension_name, std::string(), NULL, kFlagEnableFileAccess);
160 }
161
RunExtensionTestIncognito(const std::string & extension_name)162 bool ExtensionApiTest::RunExtensionTestIncognito(
163 const std::string& extension_name) {
164 return RunExtensionTestImpl(extension_name,
165 std::string(),
166 NULL,
167 kFlagEnableIncognito | kFlagEnableFileAccess);
168 }
169
RunExtensionTestIgnoreManifestWarnings(const std::string & extension_name)170 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
171 const std::string& extension_name) {
172 return RunExtensionTestImpl(
173 extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings);
174 }
175
RunExtensionTestAllowOldManifestVersion(const std::string & extension_name)176 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
177 const std::string& extension_name) {
178 return RunExtensionTestImpl(
179 extension_name,
180 std::string(),
181 NULL,
182 kFlagEnableFileAccess | kFlagAllowOldManifestVersions);
183 }
184
RunComponentExtensionTest(const std::string & extension_name)185 bool ExtensionApiTest::RunComponentExtensionTest(
186 const std::string& extension_name) {
187 return RunExtensionTestImpl(extension_name,
188 std::string(),
189 NULL,
190 kFlagEnableFileAccess | kFlagLoadAsComponent);
191 }
192
RunExtensionTestNoFileAccess(const std::string & extension_name)193 bool ExtensionApiTest::RunExtensionTestNoFileAccess(
194 const std::string& extension_name) {
195 return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone);
196 }
197
RunExtensionTestIncognitoNoFileAccess(const std::string & extension_name)198 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
199 const std::string& extension_name) {
200 return RunExtensionTestImpl(
201 extension_name, std::string(), NULL, kFlagEnableIncognito);
202 }
203
RunExtensionSubtest(const std::string & extension_name,const std::string & page_url)204 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
205 const std::string& page_url) {
206 return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess);
207 }
208
RunExtensionSubtest(const std::string & extension_name,const std::string & page_url,int flags)209 bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
210 const std::string& page_url,
211 int flags) {
212 DCHECK(!page_url.empty()) << "Argument page_url is required.";
213 // See http://crbug.com/177163 for details.
214 #if defined(OS_WIN) && !defined(NDEBUG)
215 LOG(WARNING) << "Workaround for 177163, prematurely returning";
216 return true;
217 #else
218 return RunExtensionTestImpl(extension_name, page_url, NULL, flags);
219 #endif
220 }
221
222
RunPageTest(const std::string & page_url)223 bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
224 return RunExtensionSubtest(std::string(), page_url);
225 }
226
RunPageTest(const std::string & page_url,int flags)227 bool ExtensionApiTest::RunPageTest(const std::string& page_url,
228 int flags) {
229 return RunExtensionSubtest(std::string(), page_url, flags);
230 }
231
RunPlatformAppTest(const std::string & extension_name)232 bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) {
233 return RunExtensionTestImpl(
234 extension_name, std::string(), NULL, kFlagLaunchPlatformApp);
235 }
236
RunPlatformAppTestWithArg(const std::string & extension_name,const char * custom_arg)237 bool ExtensionApiTest::RunPlatformAppTestWithArg(
238 const std::string& extension_name, const char* custom_arg) {
239 return RunExtensionTestImpl(
240 extension_name, std::string(), custom_arg, kFlagLaunchPlatformApp);
241 }
242
RunPlatformAppTestWithFlags(const std::string & extension_name,int flags)243 bool ExtensionApiTest::RunPlatformAppTestWithFlags(
244 const std::string& extension_name, int flags) {
245 return RunExtensionTestImpl(
246 extension_name, std::string(), NULL, flags | kFlagLaunchPlatformApp);
247 }
248
249 // Load |extension_name| extension and/or |page_url| and wait for
250 // PASSED or FAILED notification.
RunExtensionTestImpl(const std::string & extension_name,const std::string & page_url,const char * custom_arg,int flags)251 bool ExtensionApiTest::RunExtensionTestImpl(const std::string& extension_name,
252 const std::string& page_url,
253 const char* custom_arg,
254 int flags) {
255 bool load_as_component = (flags & kFlagLoadAsComponent) != 0;
256 bool launch_platform_app = (flags & kFlagLaunchPlatformApp) != 0;
257 bool use_incognito = (flags & kFlagUseIncognito) != 0;
258
259 if (custom_arg && custom_arg[0])
260 test_config_->SetString(kTestCustomArg, custom_arg);
261
262 extensions::ResultCatcher catcher;
263 DCHECK(!extension_name.empty() || !page_url.empty()) <<
264 "extension_name and page_url cannot both be empty";
265
266 const extensions::Extension* extension = NULL;
267 if (!extension_name.empty()) {
268 base::FilePath extension_path = test_data_dir_.AppendASCII(extension_name);
269 if (load_as_component) {
270 extension = LoadExtensionAsComponent(extension_path);
271 } else {
272 int browser_test_flags = ExtensionBrowserTest::kFlagNone;
273 if (flags & kFlagEnableIncognito)
274 browser_test_flags |= ExtensionBrowserTest::kFlagEnableIncognito;
275 if (flags & kFlagEnableFileAccess)
276 browser_test_flags |= ExtensionBrowserTest::kFlagEnableFileAccess;
277 if (flags & kFlagIgnoreManifestWarnings)
278 browser_test_flags |= ExtensionBrowserTest::kFlagIgnoreManifestWarnings;
279 if (flags & kFlagAllowOldManifestVersions) {
280 browser_test_flags |=
281 ExtensionBrowserTest::kFlagAllowOldManifestVersions;
282 }
283 extension = LoadExtensionWithFlags(extension_path, browser_test_flags);
284 }
285 if (!extension) {
286 message_ = "Failed to load extension.";
287 return false;
288 }
289 }
290
291 // If there is a page_url to load, navigate it.
292 if (!page_url.empty()) {
293 GURL url = GURL(page_url);
294
295 // Note: We use is_valid() here in the expectation that the provided url
296 // may lack a scheme & host and thus be a relative url within the loaded
297 // extension.
298 if (!url.is_valid()) {
299 DCHECK(!extension_name.empty()) <<
300 "Relative page_url given with no extension_name";
301
302 url = extension->GetResourceURL(page_url);
303 }
304
305 if (use_incognito)
306 ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url);
307 else
308 ui_test_utils::NavigateToURL(browser(), url);
309 } else if (launch_platform_app) {
310 AppLaunchParams params(browser()->profile(),
311 extension,
312 extensions::LAUNCH_CONTAINER_NONE,
313 NEW_WINDOW);
314 params.command_line = *CommandLine::ForCurrentProcess();
315 OpenApplication(params);
316 }
317
318 if (!catcher.GetNextResult()) {
319 message_ = catcher.message();
320 return false;
321 }
322
323 return true;
324 }
325
326 // Test that exactly one extension is loaded, and return it.
GetSingleLoadedExtension()327 const extensions::Extension* ExtensionApiTest::GetSingleLoadedExtension() {
328 ExtensionService* service = extensions::ExtensionSystem::Get(
329 browser()->profile())->extension_service();
330
331 const extensions::Extension* extension = NULL;
332 for (extensions::ExtensionSet::const_iterator it =
333 service->extensions()->begin();
334 it != service->extensions()->end(); ++it) {
335 // Ignore any component extensions. They are automatically loaded into all
336 // profiles and aren't the extension we're looking for here.
337 if ((*it)->location() == extensions::Manifest::COMPONENT)
338 continue;
339
340 if (extension != NULL) {
341 // TODO(yoz): this is misleading; it counts component extensions.
342 message_ = base::StringPrintf(
343 "Expected only one extension to be present. Found %u.",
344 static_cast<unsigned>(service->extensions()->size()));
345 return NULL;
346 }
347
348 extension = it->get();
349 }
350
351 if (!extension) {
352 message_ = "extension pointer is NULL.";
353 return NULL;
354 }
355 return extension;
356 }
357
StartEmbeddedTestServer()358 bool ExtensionApiTest::StartEmbeddedTestServer() {
359 if (!embedded_test_server()->InitializeAndWaitUntilReady())
360 return false;
361
362 // Build a dictionary of values that tests can use to build URLs that
363 // access the test server and local file system. Tests can see these values
364 // using the extension API function chrome.test.getConfig().
365 test_config_->SetInteger(kTestServerPort,
366 embedded_test_server()->port());
367
368 return true;
369 }
370
StartWebSocketServer(const base::FilePath & root_directory)371 bool ExtensionApiTest::StartWebSocketServer(
372 const base::FilePath& root_directory) {
373 websocket_server_.reset(new net::SpawnedTestServer(
374 net::SpawnedTestServer::TYPE_WS,
375 net::SpawnedTestServer::kLocalhost,
376 root_directory));
377
378 if (!websocket_server_->Start())
379 return false;
380
381 test_config_->SetInteger(kTestWebSocketPort,
382 websocket_server_->host_port_pair().port());
383
384 return true;
385 }
386
StartFTPServer(const base::FilePath & root_directory)387 bool ExtensionApiTest::StartFTPServer(const base::FilePath& root_directory) {
388 ftp_server_.reset(new net::SpawnedTestServer(
389 net::SpawnedTestServer::TYPE_FTP,
390 net::SpawnedTestServer::kLocalhost,
391 root_directory));
392
393 if (!ftp_server_->Start())
394 return false;
395
396 test_config_->SetInteger(kFtpServerPort,
397 ftp_server_->host_port_pair().port());
398
399 return true;
400 }
401
StartSpawnedTestServer()402 bool ExtensionApiTest::StartSpawnedTestServer() {
403 if (!test_server()->Start())
404 return false;
405
406 // Build a dictionary of values that tests can use to build URLs that
407 // access the test server and local file system. Tests can see these values
408 // using the extension API function chrome.test.getConfig().
409 test_config_->SetInteger(kSpawnedTestServerPort,
410 test_server()->host_port_pair().port());
411
412 return true;
413 }
414
SetUpCommandLine(CommandLine * command_line)415 void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
416 ExtensionBrowserTest::SetUpCommandLine(command_line);
417 test_data_dir_ = test_data_dir_.AppendASCII("api_test");
418 }
419