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