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 "base/command_line.h"
6#include "base/message_loop/message_loop.h"
7#include "base/prefs/pref_service.h"
8#include "chrome/browser/download/download_prefs.h"
9#include "chrome/browser/extensions/extension_apitest.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/ui/browser.h"
12#include "chrome/browser/ui/tabs/tab_strip_model.h"
13#include "chrome/common/extensions/api/streams_private.h"
14#include "chrome/common/extensions/manifest_handlers/mime_types_handler.h"
15#include "chrome/common/pref_names.h"
16#include "chrome/test/base/test_switches.h"
17#include "chrome/test/base/ui_test_utils.h"
18#include "content/public/browser/download_item.h"
19#include "content/public/browser/download_manager.h"
20#include "content/public/browser/render_process_host.h"
21#include "content/public/browser/resource_controller.h"
22#include "content/public/browser/web_contents.h"
23#include "content/public/test/download_test_observer.h"
24#include "extensions/browser/event_router.h"
25#include "extensions/browser/extension_system.h"
26#include "extensions/test/result_catcher.h"
27#include "net/dns/mock_host_resolver.h"
28#include "net/test/embedded_test_server/embedded_test_server.h"
29#include "net/test/embedded_test_server/http_request.h"
30#include "net/test/embedded_test_server/http_response.h"
31#include "testing/gmock/include/gmock/gmock.h"
32
33using content::BrowserContext;
34using content::BrowserThread;
35using content::DownloadItem;
36using content::DownloadManager;
37using content::DownloadUrlParameters;
38using content::ResourceController;
39using content::WebContents;
40using extensions::Event;
41using extensions::ExtensionSystem;
42using extensions::ResultCatcher;
43using net::test_server::BasicHttpResponse;
44using net::test_server::HttpRequest;
45using net::test_server::HttpResponse;
46using net::test_server::EmbeddedTestServer;
47using testing::_;
48
49namespace streams_private = extensions::api::streams_private;
50
51namespace {
52
53// Test server's request handler.
54// Returns response that should be sent by the test server.
55scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
56  scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse());
57
58  // For relative path "/doc_path.doc", return success response with MIME type
59  // "application/msword".
60  if (request.relative_url == "/doc_path.doc") {
61    response->set_code(net::HTTP_OK);
62    response->set_content_type("application/msword");
63    return response.PassAs<HttpResponse>();
64  }
65
66  // For relative path "/spreadsheet_path.xls", return success response with
67  // MIME type "application/xls".
68  if (request.relative_url == "/spreadsheet_path.xls") {
69    response->set_code(net::HTTP_OK);
70    response->set_content_type("application/msexcel");
71    // Test that multiple headers with the same name are merged.
72    response->AddCustomHeader("Test-Header", "part1");
73    response->AddCustomHeader("Test-Header", "part2");
74    return response.PassAs<HttpResponse>();
75  }
76
77  // For relative path "/text_path_attch.txt", return success response with
78  // MIME type "text/plain" and content "txt content". Also, set content
79  // disposition to be attachment.
80  if (request.relative_url == "/text_path_attch.txt") {
81    response->set_code(net::HTTP_OK);
82    response->set_content("txt content");
83    response->set_content_type("text/plain");
84    response->AddCustomHeader("Content-Disposition",
85                              "attachment; filename=test_path.txt");
86    return response.PassAs<HttpResponse>();
87  }
88
89  // For relative path "/test_path_attch.txt", return success response with
90  // MIME type "text/plain" and content "txt content".
91  if (request.relative_url == "/text_path.txt") {
92    response->set_code(net::HTTP_OK);
93    response->set_content("txt content");
94    response->set_content_type("text/plain");
95    return response.PassAs<HttpResponse>();
96  }
97
98  // A random HTML file to navigate to.
99  if (request.relative_url == "/index.html") {
100    response->set_code(net::HTTP_OK);
101    response->set_content("html content");
102    response->set_content_type("text/html");
103    return response.PassAs<HttpResponse>();
104  }
105
106  // RTF files for testing chrome.streamsPrivate.abort().
107  if (request.relative_url == "/abort.rtf" ||
108      request.relative_url == "/no_abort.rtf") {
109    response->set_code(net::HTTP_OK);
110    response->set_content_type("application/rtf");
111    return response.PassAs<HttpResponse>();
112  }
113
114  // Respond to /favicon.ico for navigating to the page.
115  if (request.relative_url == "/favicon.ico") {
116    response->set_code(net::HTTP_NOT_FOUND);
117    return response.PassAs<HttpResponse>();
118  }
119
120  // No other requests should be handled in the tests.
121  EXPECT_TRUE(false) << "NOTREACHED!";
122  response->set_code(net::HTTP_NOT_FOUND);
123  return response.PassAs<HttpResponse>();
124}
125
126// Tests to verify that resources are correctly intercepted by
127// StreamsResourceThrottle.
128// The test extension expects the resources that should be handed to the
129// extension to have MIME type 'application/msword' and the resources that
130// should be downloaded by the browser to have MIME type 'text/plain'.
131class StreamsPrivateApiTest : public ExtensionApiTest {
132 public:
133  StreamsPrivateApiTest() {}
134
135  virtual ~StreamsPrivateApiTest() {}
136
137  virtual void SetUpOnMainThread() OVERRIDE {
138    // Init test server.
139    test_server_.reset(new EmbeddedTestServer);
140    ASSERT_TRUE(test_server_->InitializeAndWaitUntilReady());
141    test_server_->RegisterRequestHandler(base::Bind(&HandleRequest));
142
143    ExtensionApiTest::SetUpOnMainThread();
144  }
145
146  virtual void TearDownOnMainThread() OVERRIDE {
147    // Tear down the test server.
148    EXPECT_TRUE(test_server_->ShutdownAndWaitUntilComplete());
149    test_server_.reset();
150    ExtensionApiTest::TearDownOnMainThread();
151  }
152
153  void InitializeDownloadSettings() {
154    ASSERT_TRUE(browser());
155    ASSERT_TRUE(downloads_dir_.CreateUniqueTempDir());
156
157    // Setup default downloads directory to the scoped tmp directory created for
158    // the test.
159    browser()->profile()->GetPrefs()->SetFilePath(
160        prefs::kDownloadDefaultDirectory, downloads_dir_.path());
161    // Ensure there are no prompts for download during the test.
162    browser()->profile()->GetPrefs()->SetBoolean(
163        prefs::kPromptForDownload, false);
164
165    DownloadManager* manager = GetDownloadManager();
166    DownloadPrefs::FromDownloadManager(manager)->ResetAutoOpen();
167    manager->RemoveAllDownloads();
168  }
169
170  // Sends onExecuteContentHandler event with the MIME type "test/done" to the
171  // test extension.
172  // The test extension calls 'chrome.test.notifySuccess' when it receives the
173  // event with the "test/done" MIME type (unless the 'chrome.test.notifyFail'
174  // has already been called).
175  void SendDoneEvent() {
176    streams_private::StreamInfo info;
177    info.mime_type = "test/done";
178    info.original_url = "http://foo";
179    info.stream_url = "blob://bar";
180    info.tab_id = 10;
181    info.expected_content_size = 20;
182
183    scoped_ptr<Event> event(
184        new Event(streams_private::OnExecuteMimeTypeHandler::kEventName,
185                  streams_private::OnExecuteMimeTypeHandler::Create(info)));
186
187    extensions::EventRouter::Get(browser()->profile())
188        ->DispatchEventToExtension(test_extension_id_, event.Pass());
189  }
190
191  // Loads the test extension and set's up its file_browser_handler to handle
192  // 'application/msword' and 'text/plain' MIME types.
193  // The extension will notify success when it detects an event with the MIME
194  // type 'application/msword' and notify fail when it detects an event with the
195  // MIME type 'text/plain'.
196  const extensions::Extension* LoadTestExtension() {
197    // The test extension id is set by the key value in the manifest.
198    test_extension_id_ = "oickdpebdnfbgkcaoklfcdhjniefkcji";
199
200    const extensions::Extension* extension = LoadExtension(
201        test_data_dir_.AppendASCII("streams_private/handle_mime_type"));
202    if (!extension)
203      return NULL;
204
205    MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension);
206    if (!handler) {
207      message_ = "No mime type handlers defined.";
208      return NULL;
209    }
210
211    DCHECK_EQ(test_extension_id_, extension->id());
212
213    return extension;
214  }
215
216  // Returns the download manager for the current browser.
217  DownloadManager* GetDownloadManager() const {
218    DownloadManager* download_manager =
219        BrowserContext::GetDownloadManager(browser()->profile());
220    EXPECT_TRUE(download_manager);
221    return download_manager;
222  }
223
224  // Deletes the download and waits until it's flushed.
225  // The |manager| should have |download| in its list of downloads.
226  void DeleteDownloadAndWaitForFlush(DownloadItem* download,
227                                     DownloadManager* manager) {
228    scoped_refptr<content::DownloadTestFlushObserver> flush_observer(
229        new content::DownloadTestFlushObserver(manager));
230    download->Remove();
231    flush_observer->WaitForFlush();
232  }
233
234 protected:
235  std::string test_extension_id_;
236  // The HTTP server used in the tests.
237  scoped_ptr<EmbeddedTestServer> test_server_;
238  base::ScopedTempDir downloads_dir_;
239};
240
241// Tests that navigating to a resource with a MIME type handleable by an
242// installed, white-listed extension invokes the extension's
243// onExecuteContentHandler event (and does not start a download).
244IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Navigate) {
245#if defined(OS_WIN) && defined(USE_ASH)
246  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
247  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
248    return;
249#endif
250
251  ASSERT_TRUE(LoadTestExtension()) << message_;
252
253  ResultCatcher catcher;
254
255  ui_test_utils::NavigateToURL(browser(),
256                               test_server_->GetURL("/doc_path.doc"));
257
258  // Wait for the response from the test server.
259  base::MessageLoop::current()->RunUntilIdle();
260
261  // There should be no downloads started by the navigation.
262  DownloadManager* download_manager = GetDownloadManager();
263  std::vector<DownloadItem*> downloads;
264  download_manager->GetAllDownloads(&downloads);
265  ASSERT_EQ(0u, downloads.size());
266
267  // The test extension should receive onExecuteContentHandler event with MIME
268  // type 'application/msword' (and call chrome.test.notifySuccess).
269  EXPECT_TRUE(catcher.GetNextResult());
270}
271
272// Tests that navigating cross-site to a resource with a MIME type handleable by
273// an installed, white-listed extension invokes the extension's
274// onExecuteContentHandler event (and does not start a download).
275// Regression test for http://crbug.com/342999.
276IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, NavigateCrossSite) {
277#if defined(OS_WIN) && defined(USE_ASH)
278  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
279  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
280    return;
281#endif
282
283  ASSERT_TRUE(LoadTestExtension()) << message_;
284
285  ResultCatcher catcher;
286
287  // Navigate to a URL on a different hostname.
288  std::string initial_host = "www.example.com";
289  host_resolver()->AddRule(initial_host, "127.0.0.1");
290  GURL::Replacements replacements;
291  replacements.SetHostStr(initial_host);
292  GURL initial_url =
293      test_server_->GetURL("/index.html").ReplaceComponents(replacements);
294  ui_test_utils::NavigateToURL(browser(), initial_url);
295
296  // Now navigate to the doc file; the extension should pick it up normally.
297  ui_test_utils::NavigateToURL(browser(),
298                               test_server_->GetURL("/doc_path.doc"));
299
300  // Wait for the response from the test server.
301  base::MessageLoop::current()->RunUntilIdle();
302
303  // There should be no downloads started by the navigation.
304  DownloadManager* download_manager = GetDownloadManager();
305  std::vector<DownloadItem*> downloads;
306  download_manager->GetAllDownloads(&downloads);
307  ASSERT_EQ(0u, downloads.size());
308
309  // The test extension should receive onExecuteContentHandler event with MIME
310  // type 'application/msword' (and call chrome.test.notifySuccess).
311  EXPECT_TRUE(catcher.GetNextResult());
312}
313
314// Tests that navigation to an attachment starts a download, even if there is an
315// extension with a file browser handler that can handle the attachment's MIME
316// type.
317IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, NavigateToAnAttachment) {
318  InitializeDownloadSettings();
319
320  ASSERT_TRUE(LoadTestExtension()) << message_;
321
322  ResultCatcher catcher;
323
324  // The test should start a download.
325  DownloadManager* download_manager = GetDownloadManager();
326  scoped_ptr<content::DownloadTestObserver> download_observer(
327      new content::DownloadTestObserverInProgress(download_manager, 1));
328
329  ui_test_utils::NavigateToURL(browser(),
330                               test_server_->GetURL("/text_path_attch.txt"));
331
332  // Wait for the download to start.
333  download_observer->WaitForFinished();
334
335  // There should be one download started by the navigation.
336  DownloadManager::DownloadVector downloads;
337  download_manager->GetAllDownloads(&downloads);
338  ASSERT_EQ(1u, downloads.size());
339
340  // Cancel and delete the download started in the test.
341  DeleteDownloadAndWaitForFlush(downloads[0], download_manager);
342
343  // The test extension should not receive any events by now. Send it an event
344  // with MIME type "test/done", so it stops waiting for the events. (If there
345  // was an event with MIME type 'text/plain', |catcher.GetNextResult()| will
346  // fail regardless of the sent event; chrome.test.notifySuccess will not be
347  // called by the extension).
348  SendDoneEvent();
349  EXPECT_TRUE(catcher.GetNextResult());
350}
351
352// Tests that direct download requests don't get intercepted by
353// StreamsResourceThrottle, even if there is an extension with a file
354// browser handler that can handle the download's MIME type.
355IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, DirectDownload) {
356  InitializeDownloadSettings();
357
358  ASSERT_TRUE(LoadTestExtension()) << message_;
359
360  ResultCatcher catcher;
361
362  DownloadManager* download_manager = GetDownloadManager();
363  scoped_ptr<content::DownloadTestObserver> download_observer(
364      new content::DownloadTestObserverInProgress(download_manager, 1));
365
366  // The resource's URL on the test server.
367  GURL url = test_server_->GetURL("/text_path.txt");
368
369  // The download's target file path.
370  base::FilePath target_path =
371      downloads_dir_.path().Append(FILE_PATH_LITERAL("download_target.txt"));
372
373  // Set the downloads parameters.
374  content::WebContents* web_contents =
375      browser()->tab_strip_model()->GetActiveWebContents();
376  ASSERT_TRUE(web_contents);
377  scoped_ptr<DownloadUrlParameters> params(
378      DownloadUrlParameters::FromWebContents(web_contents, url));
379  params->set_file_path(target_path);
380
381  // Start download of the URL with a path "/text_path.txt" on the test server.
382  download_manager->DownloadUrl(params.Pass());
383
384  // Wait for the download to start.
385  download_observer->WaitForFinished();
386
387  // There should have been one download.
388  std::vector<DownloadItem*> downloads;
389  download_manager->GetAllDownloads(&downloads);
390  ASSERT_EQ(1u, downloads.size());
391
392  // Cancel and delete the download statred in the test.
393  DeleteDownloadAndWaitForFlush(downloads[0], download_manager);
394
395  // The test extension should not receive any events by now. Send it an event
396  // with MIME type "test/done", so it stops waiting for the events. (If there
397  // was an event with MIME type 'text/plain', |catcher.GetNextResult()| will
398  // fail regardless of the sent event; chrome.test.notifySuccess will not be
399  // called by the extension).
400  SendDoneEvent();
401  EXPECT_TRUE(catcher.GetNextResult());
402}
403
404// Tests that response headers are correctly passed to the API and that multiple
405// repsonse headers with the same name are merged correctly.
406IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Headers) {
407#if defined(OS_WIN) && defined(USE_ASH)
408  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
409  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
410    return;
411#endif
412
413  ASSERT_TRUE(LoadTestExtension()) << message_;
414
415  ResultCatcher catcher;
416
417  ui_test_utils::NavigateToURL(browser(),
418                               test_server_->GetURL("/spreadsheet_path.xls"));
419
420  // Wait for the response from the test server.
421  base::MessageLoop::current()->RunUntilIdle();
422
423  // There should be no downloads started by the navigation.
424  DownloadManager* download_manager = GetDownloadManager();
425  std::vector<DownloadItem*> downloads;
426  download_manager->GetAllDownloads(&downloads);
427  ASSERT_EQ(0u, downloads.size());
428
429  // The test extension should receive onExecuteContentHandler event with MIME
430  // type 'application/msexcel' (and call chrome.test.notifySuccess).
431  EXPECT_TRUE(catcher.GetNextResult());
432}
433
434// Tests that chrome.streamsPrivate.abort() works correctly.
435IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Abort) {
436#if defined(OS_WIN) && defined(USE_ASH)
437  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
438  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
439    return;
440#endif
441
442  ASSERT_TRUE(LoadTestExtension()) << message_;
443
444  ResultCatcher catcher;
445  ui_test_utils::NavigateToURL(browser(),
446                               test_server_->GetURL("/no_abort.rtf"));
447  base::MessageLoop::current()->RunUntilIdle();
448  EXPECT_TRUE(catcher.GetNextResult());
449
450  ui_test_utils::NavigateToURL(browser(),
451                               test_server_->GetURL("/abort.rtf"));
452  base::MessageLoop::current()->RunUntilIdle();
453  EXPECT_TRUE(catcher.GetNextResult());
454}
455
456}  // namespace
457