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.
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/pref_names.h"
15 #include "chrome/test/base/test_switches.h"
16 #include "chrome/test/base/ui_test_utils.h"
17 #include "content/public/browser/download_item.h"
18 #include "content/public/browser/download_manager.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/resource_controller.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/test/download_test_observer.h"
23 #include "extensions/browser/event_router.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/manifest_handlers/mime_types_handler.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"
33 using content::BrowserContext
;
34 using content::BrowserThread
;
35 using content::DownloadItem
;
36 using content::DownloadManager
;
37 using content::DownloadUrlParameters
;
38 using content::ResourceController
;
39 using content::WebContents
;
40 using extensions::Event
;
41 using extensions::ExtensionSystem
;
42 using extensions::ResultCatcher
;
43 using net::test_server::BasicHttpResponse
;
44 using net::test_server::HttpRequest
;
45 using net::test_server::HttpResponse
;
46 using net::test_server::EmbeddedTestServer
;
49 namespace streams_private
= extensions::api::streams_private
;
53 // Test server's request handler.
54 // Returns response that should be sent by the test server.
55 scoped_ptr
<HttpResponse
> HandleRequest(const HttpRequest
& request
) {
56 scoped_ptr
<BasicHttpResponse
> response(new BasicHttpResponse());
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
.Pass();
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
.Pass();
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
.Pass();
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
.Pass();
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
.Pass();
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
.Pass();
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
.Pass();
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
.Pass();
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'.
131 class StreamsPrivateApiTest
: public ExtensionApiTest
{
133 StreamsPrivateApiTest() {}
135 ~StreamsPrivateApiTest() override
{}
137 void SetUpOnMainThread() override
{
139 test_server_
.reset(new EmbeddedTestServer
);
140 ASSERT_TRUE(test_server_
->InitializeAndWaitUntilReady());
141 test_server_
->RegisterRequestHandler(base::Bind(&HandleRequest
));
143 ExtensionApiTest::SetUpOnMainThread();
146 void TearDownOnMainThread() override
{
147 // Tear down the test server.
148 EXPECT_TRUE(test_server_
->ShutdownAndWaitUntilComplete());
149 test_server_
.reset();
150 ExtensionApiTest::TearDownOnMainThread();
153 void InitializeDownloadSettings() {
154 ASSERT_TRUE(browser());
155 ASSERT_TRUE(downloads_dir_
.CreateUniqueTempDir());
157 // Setup default downloads directory to the scoped tmp directory created for
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);
165 DownloadManager
* manager
= GetDownloadManager();
166 DownloadPrefs::FromDownloadManager(manager
)->ResetAutoOpen();
167 manager
->RemoveAllDownloads();
170 // Sends onExecuteContentHandler event with the MIME type "test/done" to the
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";
181 info
.expected_content_size
= 20;
183 scoped_ptr
<Event
> event(new Event(
184 extensions::events::STREAMS_PRIVATE_ON_EXECUTE_MIME_TYPE_HANDLER
,
185 streams_private::OnExecuteMimeTypeHandler::kEventName
,
186 streams_private::OnExecuteMimeTypeHandler::Create(info
)));
188 extensions::EventRouter::Get(browser()->profile())
189 ->DispatchEventToExtension(test_extension_id_
, event
.Pass());
192 // Loads the test extension and set's up its file_browser_handler to handle
193 // 'application/msword' and 'text/plain' MIME types.
194 // The extension will notify success when it detects an event with the MIME
195 // type 'application/msword' and notify fail when it detects an event with the
196 // MIME type 'text/plain'.
197 const extensions::Extension
* LoadTestExtension() {
198 // The test extension id is set by the key value in the manifest.
199 test_extension_id_
= "oickdpebdnfbgkcaoklfcdhjniefkcji";
201 const extensions::Extension
* extension
= LoadExtension(
202 test_data_dir_
.AppendASCII("streams_private/handle_mime_type"));
206 MimeTypesHandler
* handler
= MimeTypesHandler::GetHandler(extension
);
208 message_
= "No mime type handlers defined.";
212 DCHECK_EQ(test_extension_id_
, extension
->id());
217 // Returns the download manager for the current browser.
218 DownloadManager
* GetDownloadManager() const {
219 DownloadManager
* download_manager
=
220 BrowserContext::GetDownloadManager(browser()->profile());
221 EXPECT_TRUE(download_manager
);
222 return download_manager
;
225 // Deletes the download and waits until it's flushed.
226 // The |manager| should have |download| in its list of downloads.
227 void DeleteDownloadAndWaitForFlush(DownloadItem
* download
,
228 DownloadManager
* manager
) {
229 scoped_refptr
<content::DownloadTestFlushObserver
> flush_observer(
230 new content::DownloadTestFlushObserver(manager
));
232 flush_observer
->WaitForFlush();
236 std::string test_extension_id_
;
237 // The HTTP server used in the tests.
238 scoped_ptr
<EmbeddedTestServer
> test_server_
;
239 base::ScopedTempDir downloads_dir_
;
242 // Tests that navigating to a resource with a MIME type handleable by an
243 // installed, white-listed extension invokes the extension's
244 // onExecuteContentHandler event (and does not start a download).
245 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, Navigate
) {
246 #if defined(OS_WIN) && defined(USE_ASH)
247 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
248 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
249 switches::kAshBrowserTests
))
253 ASSERT_TRUE(LoadTestExtension()) << message_
;
255 ResultCatcher catcher
;
257 ui_test_utils::NavigateToURL(browser(),
258 test_server_
->GetURL("/doc_path.doc"));
260 // Wait for the response from the test server.
261 base::MessageLoop::current()->RunUntilIdle();
263 // There should be no downloads started by the navigation.
264 DownloadManager
* download_manager
= GetDownloadManager();
265 std::vector
<DownloadItem
*> downloads
;
266 download_manager
->GetAllDownloads(&downloads
);
267 ASSERT_EQ(0u, downloads
.size());
269 // The test extension should receive onExecuteContentHandler event with MIME
270 // type 'application/msword' (and call chrome.test.notifySuccess).
271 EXPECT_TRUE(catcher
.GetNextResult());
274 // Tests that navigating to a file URL also intercepts despite there being no
275 // HTTP headers. This is a regression test for https://crbug.com/416433.
276 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, FileURL
) {
277 #if defined(OS_WIN) && defined(USE_ASH)
278 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
279 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
280 switches::kAshBrowserTests
))
284 ASSERT_TRUE(LoadTestExtension()) << message_
;
286 ResultCatcher catcher
;
288 ui_test_utils::NavigateToURL(browser(), ui_test_utils::GetTestUrl(
289 base::FilePath(FILE_PATH_LITERAL("downloads")),
290 base::FilePath(FILE_PATH_LITERAL("Picture_1.doc"))));
292 // There should be no downloads started by the navigation.
293 DownloadManager
* download_manager
= GetDownloadManager();
294 std::vector
<DownloadItem
*> downloads
;
295 download_manager
->GetAllDownloads(&downloads
);
296 ASSERT_EQ(0u, downloads
.size());
298 // The test extension should receive onExecuteContentHandler event with MIME
299 // type 'application/msword' (and call chrome.test.notifySuccess).
300 EXPECT_TRUE(catcher
.GetNextResult());
303 // Tests that navigating cross-site to a resource with a MIME type handleable by
304 // an installed, white-listed extension invokes the extension's
305 // onExecuteContentHandler event (and does not start a download).
306 // Regression test for http://crbug.com/342999.
307 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, NavigateCrossSite
) {
308 #if defined(OS_WIN) && defined(USE_ASH)
309 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
310 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
311 switches::kAshBrowserTests
))
315 ASSERT_TRUE(LoadTestExtension()) << message_
;
317 ResultCatcher catcher
;
319 // Navigate to a URL on a different hostname.
320 static const char kInitialHost
[] = "www.example.com";
321 host_resolver()->AddRule(kInitialHost
, "127.0.0.1");
322 GURL::Replacements replacements
;
323 replacements
.SetHostStr(kInitialHost
);
325 test_server_
->GetURL("/index.html").ReplaceComponents(replacements
);
326 ui_test_utils::NavigateToURL(browser(), initial_url
);
328 // Now navigate to the doc file; the extension should pick it up normally.
329 ui_test_utils::NavigateToURL(browser(),
330 test_server_
->GetURL("/doc_path.doc"));
332 // Wait for the response from the test server.
333 base::MessageLoop::current()->RunUntilIdle();
335 // There should be no downloads started by the navigation.
336 DownloadManager
* download_manager
= GetDownloadManager();
337 std::vector
<DownloadItem
*> downloads
;
338 download_manager
->GetAllDownloads(&downloads
);
339 ASSERT_EQ(0u, downloads
.size());
341 // The test extension should receive onExecuteContentHandler event with MIME
342 // type 'application/msword' (and call chrome.test.notifySuccess).
343 EXPECT_TRUE(catcher
.GetNextResult());
346 // Tests that navigation to an attachment starts a download, even if there is an
347 // extension with a file browser handler that can handle the attachment's MIME
349 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, NavigateToAnAttachment
) {
350 InitializeDownloadSettings();
352 ASSERT_TRUE(LoadTestExtension()) << message_
;
354 ResultCatcher catcher
;
356 // The test should start a download.
357 DownloadManager
* download_manager
= GetDownloadManager();
358 scoped_ptr
<content::DownloadTestObserver
> download_observer(
359 new content::DownloadTestObserverInProgress(download_manager
, 1));
361 ui_test_utils::NavigateToURL(browser(),
362 test_server_
->GetURL("/text_path_attch.txt"));
364 // Wait for the download to start.
365 download_observer
->WaitForFinished();
367 // There should be one download started by the navigation.
368 DownloadManager::DownloadVector downloads
;
369 download_manager
->GetAllDownloads(&downloads
);
370 ASSERT_EQ(1u, downloads
.size());
372 // Cancel and delete the download started in the test.
373 DeleteDownloadAndWaitForFlush(downloads
[0], download_manager
);
375 // The test extension should not receive any events by now. Send it an event
376 // with MIME type "test/done", so it stops waiting for the events. (If there
377 // was an event with MIME type 'text/plain', |catcher.GetNextResult()| will
378 // fail regardless of the sent event; chrome.test.notifySuccess will not be
379 // called by the extension).
381 EXPECT_TRUE(catcher
.GetNextResult());
384 // Tests that direct download requests don't get intercepted by
385 // StreamsResourceThrottle, even if there is an extension with a file
386 // browser handler that can handle the download's MIME type.
387 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, DirectDownload
) {
388 InitializeDownloadSettings();
390 ASSERT_TRUE(LoadTestExtension()) << message_
;
392 ResultCatcher catcher
;
394 DownloadManager
* download_manager
= GetDownloadManager();
395 scoped_ptr
<content::DownloadTestObserver
> download_observer(
396 new content::DownloadTestObserverInProgress(download_manager
, 1));
398 // The resource's URL on the test server.
399 GURL url
= test_server_
->GetURL("/text_path.txt");
401 // The download's target file path.
402 base::FilePath target_path
=
403 downloads_dir_
.path().Append(FILE_PATH_LITERAL("download_target.txt"));
405 // Set the downloads parameters.
406 content::WebContents
* web_contents
=
407 browser()->tab_strip_model()->GetActiveWebContents();
408 ASSERT_TRUE(web_contents
);
409 scoped_ptr
<DownloadUrlParameters
> params(
410 DownloadUrlParameters::FromWebContents(web_contents
, url
));
411 params
->set_file_path(target_path
);
413 // Start download of the URL with a path "/text_path.txt" on the test server.
414 download_manager
->DownloadUrl(params
.Pass());
416 // Wait for the download to start.
417 download_observer
->WaitForFinished();
419 // There should have been one download.
420 std::vector
<DownloadItem
*> downloads
;
421 download_manager
->GetAllDownloads(&downloads
);
422 ASSERT_EQ(1u, downloads
.size());
424 // Cancel and delete the download statred in the test.
425 DeleteDownloadAndWaitForFlush(downloads
[0], download_manager
);
427 // The test extension should not receive any events by now. Send it an event
428 // with MIME type "test/done", so it stops waiting for the events. (If there
429 // was an event with MIME type 'text/plain', |catcher.GetNextResult()| will
430 // fail regardless of the sent event; chrome.test.notifySuccess will not be
431 // called by the extension).
433 EXPECT_TRUE(catcher
.GetNextResult());
436 // Tests that response headers are correctly passed to the API and that multiple
437 // repsonse headers with the same name are merged correctly.
438 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, Headers
) {
439 #if defined(OS_WIN) && defined(USE_ASH)
440 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
441 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
442 switches::kAshBrowserTests
))
446 ASSERT_TRUE(LoadTestExtension()) << message_
;
448 ResultCatcher catcher
;
450 ui_test_utils::NavigateToURL(browser(),
451 test_server_
->GetURL("/spreadsheet_path.xls"));
453 // Wait for the response from the test server.
454 base::MessageLoop::current()->RunUntilIdle();
456 // There should be no downloads started by the navigation.
457 DownloadManager
* download_manager
= GetDownloadManager();
458 std::vector
<DownloadItem
*> downloads
;
459 download_manager
->GetAllDownloads(&downloads
);
460 ASSERT_EQ(0u, downloads
.size());
462 // The test extension should receive onExecuteContentHandler event with MIME
463 // type 'application/msexcel' (and call chrome.test.notifySuccess).
464 EXPECT_TRUE(catcher
.GetNextResult());
467 // Tests that chrome.streamsPrivate.abort() works correctly.
468 IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest
, Abort
) {
469 #if defined(OS_WIN) && defined(USE_ASH)
470 // Disable this test in Metro+Ash for now (http://crbug.com/262796).
471 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
472 switches::kAshBrowserTests
))
476 ASSERT_TRUE(LoadTestExtension()) << message_
;
478 ResultCatcher catcher
;
479 ui_test_utils::NavigateToURL(browser(),
480 test_server_
->GetURL("/no_abort.rtf"));
481 base::MessageLoop::current()->RunUntilIdle();
482 EXPECT_TRUE(catcher
.GetNextResult());
484 ui_test_utils::NavigateToURL(browser(),
485 test_server_
->GetURL("/abort.rtf"));
486 base::MessageLoop::current()->RunUntilIdle();
487 EXPECT_TRUE(catcher
.GetNextResult());