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
28namespace {
29
30const char kTestCustomArg[] = "customArg";
31const char kTestServerPort[] = "testServer.port";
32const char kTestDataDirectory[] = "testDataDirectory";
33const char kTestWebSocketPort[] = "testWebSocketPort";
34const char kFtpServerPort[] = "ftpServer.port";
35const char kSpawnedTestServerPort[] = "spawnedTestServer.port";
36
37scoped_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
53scoped_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
75scoped_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
97scoped_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
129ExtensionApiTest::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
140ExtensionApiTest::~ExtensionApiTest() {}
141
142void 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
152void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
153  extensions::TestGetConfigFunction::set_test_config_state(NULL);
154  test_config_.reset(NULL);
155}
156
157bool ExtensionApiTest::RunExtensionTest(const std::string& extension_name) {
158  return RunExtensionTestImpl(
159      extension_name, std::string(), NULL, kFlagEnableFileAccess);
160}
161
162bool ExtensionApiTest::RunExtensionTestIncognito(
163    const std::string& extension_name) {
164  return RunExtensionTestImpl(extension_name,
165                              std::string(),
166                              NULL,
167                              kFlagEnableIncognito | kFlagEnableFileAccess);
168}
169
170bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
171    const std::string& extension_name) {
172  return RunExtensionTestImpl(
173      extension_name, std::string(), NULL, kFlagIgnoreManifestWarnings);
174}
175
176bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
177    const std::string& extension_name) {
178  return RunExtensionTestImpl(
179      extension_name,
180      std::string(),
181      NULL,
182      kFlagEnableFileAccess | kFlagAllowOldManifestVersions);
183}
184
185bool ExtensionApiTest::RunComponentExtensionTest(
186    const std::string& extension_name) {
187  return RunExtensionTestImpl(extension_name,
188                              std::string(),
189                              NULL,
190                              kFlagEnableFileAccess | kFlagLoadAsComponent);
191}
192
193bool ExtensionApiTest::RunExtensionTestNoFileAccess(
194    const std::string& extension_name) {
195  return RunExtensionTestImpl(extension_name, std::string(), NULL, kFlagNone);
196}
197
198bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
199    const std::string& extension_name) {
200  return RunExtensionTestImpl(
201      extension_name, std::string(), NULL, kFlagEnableIncognito);
202}
203
204bool ExtensionApiTest::RunExtensionSubtest(const std::string& extension_name,
205                                           const std::string& page_url) {
206  return RunExtensionSubtest(extension_name, page_url, kFlagEnableFileAccess);
207}
208
209bool 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
223bool ExtensionApiTest::RunPageTest(const std::string& page_url) {
224  return RunExtensionSubtest(std::string(), page_url);
225}
226
227bool ExtensionApiTest::RunPageTest(const std::string& page_url,
228                                   int flags) {
229  return RunExtensionSubtest(std::string(), page_url, flags);
230}
231
232bool ExtensionApiTest::RunPlatformAppTest(const std::string& extension_name) {
233  return RunExtensionTestImpl(
234      extension_name, std::string(), NULL, kFlagLaunchPlatformApp);
235}
236
237bool 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
243bool 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.
251bool 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.
327const 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
358bool 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
371bool 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
387bool 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
402bool 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
415void ExtensionApiTest::SetUpCommandLine(CommandLine* command_line) {
416  ExtensionBrowserTest::SetUpCommandLine(command_line);
417  test_data_dir_ = test_data_dir_.AppendASCII("api_test");
418}
419