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/event_router.h"
10#include "chrome/browser/extensions/extension_apitest.h"
11#include "chrome/browser/extensions/extension_info_map.h"
12#include "chrome/browser/extensions/extension_system.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/tabs/tab_strip_model.h"
16#include "chrome/common/extensions/mime_types_handler.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/test/base/test_switches.h"
19#include "chrome/test/base/ui_test_utils.h"
20#include "content/public/browser/download_item.h"
21#include "content/public/browser/download_manager.h"
22#include "content/public/browser/render_process_host.h"
23#include "content/public/browser/resource_controller.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/test/download_test_observer.h"
26#include "net/test/embedded_test_server/embedded_test_server.h"
27#include "net/test/embedded_test_server/http_request.h"
28#include "net/test/embedded_test_server/http_response.h"
29#include "testing/gmock/include/gmock/gmock.h"
30
31using content::BrowserContext;
32using content::BrowserThread;
33using content::DownloadItem;
34using content::DownloadManager;
35using content::DownloadUrlParameters;
36using content::ResourceController;
37using content::WebContents;
38using extensions::Event;
39using extensions::ExtensionSystem;
40using net::test_server::BasicHttpResponse;
41using net::test_server::HttpRequest;
42using net::test_server::HttpResponse;
43using net::test_server::EmbeddedTestServer;
44using testing::_;
45
46namespace {
47
48// Test server's request handler.
49// Returns response that should be sent by the test server.
50scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
51  scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse());
52
53  // For relative path "/doc_path.doc", return success response with MIME type
54  // "application/msword".
55  if (request.relative_url == "/doc_path.doc") {
56    response->set_code(net::HTTP_OK);
57    response->set_content_type("application/msword");
58    return response.PassAs<HttpResponse>();
59  }
60
61  // For relative path "/test_path_attch.txt", return success response with
62  // MIME type "plain/text" and content "txt content". Also, set content
63  // disposition to be attachment.
64  if (request.relative_url == "/text_path_attch.txt") {
65    response->set_code(net::HTTP_OK);
66    response->set_content("txt content");
67    response->set_content_type("plain/text");
68    response->AddCustomHeader("Content-Disposition",
69                              "attachment; filename=test_path.txt");
70    return response.PassAs<HttpResponse>();
71  }
72  // For relative path "/test_path_attch.txt", return success response with
73  // MIME type "plain/text" and content "txt content".
74  if (request.relative_url == "/text_path.txt") {
75    response->set_code(net::HTTP_OK);
76    response->set_content("txt content");
77    response->set_content_type("plain/text");
78    return response.PassAs<HttpResponse>();
79  }
80
81  // No other requests should be handled in the tests.
82  EXPECT_TRUE(false) << "NOTREACHED!";
83  response->set_code(net::HTTP_NOT_FOUND);
84  return response.PassAs<HttpResponse>();
85}
86
87// Tests to verify that resources are correctly intercepted by
88// StreamsResourceThrottle.
89// The test extension expects the resources that should be handed to the
90// extension to have MIME type 'application/msword' and the resources that
91// should be downloaded by the browser to have MIME type 'plain/text'.
92class StreamsPrivateApiTest : public ExtensionApiTest {
93 public:
94  StreamsPrivateApiTest() {}
95
96  virtual ~StreamsPrivateApiTest() {}
97
98  virtual void SetUpOnMainThread() OVERRIDE {
99    // Init test server.
100    test_server_.reset(new EmbeddedTestServer(
101                           content::BrowserThread::GetMessageLoopProxyForThread(
102                               content::BrowserThread::IO)));
103    ASSERT_TRUE(test_server_->InitializeAndWaitUntilReady());
104    test_server_->RegisterRequestHandler(base::Bind(&HandleRequest));
105
106    ExtensionApiTest::SetUpOnMainThread();
107  }
108
109  virtual void CleanUpOnMainThread() OVERRIDE {
110    // Tear down the test server.
111    EXPECT_TRUE(test_server_->ShutdownAndWaitUntilComplete());
112    test_server_.reset();
113    ExtensionApiTest::CleanUpOnMainThread();
114  }
115
116  void InitializeDownloadSettings() {
117    ASSERT_TRUE(browser());
118    ASSERT_TRUE(downloads_dir_.CreateUniqueTempDir());
119
120    // Setup default downloads directory to the scoped tmp directory created for
121    // the test.
122    browser()->profile()->GetPrefs()->SetFilePath(
123        prefs::kDownloadDefaultDirectory, downloads_dir_.path());
124    // Ensure there are no prompts for download during the test.
125    browser()->profile()->GetPrefs()->SetBoolean(
126        prefs::kPromptForDownload, false);
127
128    DownloadManager* manager = GetDownloadManager();
129    DownloadPrefs::FromDownloadManager(manager)->ResetAutoOpen();
130    manager->RemoveAllDownloads();
131  }
132
133  // Sends onExecuteContentHandler event with the MIME type "test/done" to the
134  // test extension.
135  // The test extension calls 'chrome.test.notifySuccess' when it receives the
136  // event with the "test/done" MIME type (unless the 'chrome.test.notifyFail'
137  // has already been called).
138  void SendDoneEvent() {
139    scoped_ptr<base::ListValue> event_args(new base::ListValue());
140    event_args->Append(new base::StringValue("test/done"));
141    event_args->Append(new base::StringValue("http://foo"));
142    event_args->Append(new base::StringValue("blob://bar"));
143    event_args->Append(new base::FundamentalValue(10));
144    event_args->Append(new base::FundamentalValue(20));
145
146    scoped_ptr<Event> event(new Event(
147        "streamsPrivate.onExecuteMimeTypeHandler", event_args.Pass()));
148
149    ExtensionSystem::Get(browser()->profile())->event_router()->
150        DispatchEventToExtension(test_extension_id_, event.Pass());
151  }
152
153  // Loads the test extension and set's up its file_browser_handler to handle
154  // 'application/msword' and 'plain/text' MIME types.
155  // The extension will notify success when it detects an event with the MIME
156  // type 'application/msword' and notify fail when it detects an event with the
157  // MIME type 'plain/text'.
158  const extensions::Extension* LoadTestExtension() {
159    // The test extension id is set by the key value in the manifest.
160    test_extension_id_ = "oickdpebdnfbgkcaoklfcdhjniefkcji";
161
162    const extensions::Extension* extension = LoadExtension(
163        test_data_dir_.AppendASCII("streams_private/handle_mime_type"));
164    if (!extension)
165      return NULL;
166
167    MimeTypesHandler* handler = MimeTypesHandler::GetHandler(extension);
168    if (!handler) {
169      message_ = "No mime type handlers defined.";
170      return NULL;
171    }
172
173    DCHECK_EQ(test_extension_id_, extension->id());
174
175    return extension;
176  }
177
178  // Returns the download manager for the current browser.
179  DownloadManager* GetDownloadManager() const {
180    DownloadManager* download_manager =
181        BrowserContext::GetDownloadManager(browser()->profile());
182    EXPECT_TRUE(download_manager);
183    return download_manager;
184  }
185
186  // Deletes the download and waits until it's flushed.
187  // The |manager| should have |download| in its list of downloads.
188  void DeleteDownloadAndWaitForFlush(DownloadItem* download,
189                                     DownloadManager* manager) {
190    scoped_refptr<content::DownloadTestFlushObserver> flush_observer(
191        new content::DownloadTestFlushObserver(manager));
192    download->Remove();
193    flush_observer->WaitForFlush();
194  }
195
196 protected:
197  std::string test_extension_id_;
198  // The HTTP server used in the tests.
199  scoped_ptr<EmbeddedTestServer> test_server_;
200  base::ScopedTempDir downloads_dir_;
201};
202
203// Tests that navigating to a resource with a MIME type handleable by an
204// installed, white-listed extension invokes the extension's
205// onExecuteContentHandler event (and does not start a download).
206IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, Navigate) {
207#if defined(OS_WIN) && defined(USE_ASH)
208  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
209  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
210    return;
211#endif
212
213  ASSERT_TRUE(LoadTestExtension()) << message_;
214
215  ResultCatcher catcher;
216
217  ui_test_utils::NavigateToURL(browser(),
218                               test_server_->GetURL("/doc_path.doc"));
219
220  // Wait for the response from the test server.
221  base::MessageLoop::current()->RunUntilIdle();
222
223  // There should be no downloads started by the navigation.
224  DownloadManager* download_manager = GetDownloadManager();
225  std::vector<DownloadItem*> downloads;
226  download_manager->GetAllDownloads(&downloads);
227  ASSERT_EQ(0u, downloads.size());
228
229  // The test extension should receive onExecuteContentHandler event with MIME
230  // type 'application/msword' (and call chrome.test.notifySuccess).
231  EXPECT_TRUE(catcher.GetNextResult());
232}
233
234// Tests that navigation to an attachment starts a download, even if there is an
235// extension with a file browser handler that can handle the attachment's MIME
236// type.
237IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, NavigateToAnAttachment) {
238  InitializeDownloadSettings();
239
240  ASSERT_TRUE(LoadTestExtension()) << message_;
241
242  ResultCatcher catcher;
243
244  // The test should start a downloadm.
245  DownloadManager* download_manager = GetDownloadManager();
246  scoped_ptr<content::DownloadTestObserver> download_observer(
247      new content::DownloadTestObserverInProgress(download_manager, 1));
248
249  ui_test_utils::NavigateToURL(browser(),
250                               test_server_->GetURL("/text_path_attch.txt"));
251
252  // Wait for the download to start.
253  download_observer->WaitForFinished();
254
255  // There should be one download started by the navigation.
256  DownloadManager::DownloadVector downloads;
257  download_manager->GetAllDownloads(&downloads);
258  ASSERT_EQ(1u, downloads.size());
259
260  // Cancel and delete the download started in the test.
261  DeleteDownloadAndWaitForFlush(downloads[0], download_manager);
262
263  // The test extension should not receive any events by now. Send it an event
264  // with MIME type "test/done", so it stops waiting for the events. (If there
265  // was an event with MIME type 'plain/text', |catcher.GetNextResult()| will
266  // fail regardless of the sent event; chrome.test.notifySuccess will not be
267  // called by the extension).
268  SendDoneEvent();
269  EXPECT_TRUE(catcher.GetNextResult());
270}
271
272// Tests that direct download requests don't get intercepted by
273// StreamsResourceThrottle, even if there is an extension with a file
274// browser handler that can handle the download's MIME type.
275IN_PROC_BROWSER_TEST_F(StreamsPrivateApiTest, DirectDownload) {
276  InitializeDownloadSettings();
277
278  ASSERT_TRUE(LoadTestExtension()) << message_;
279
280  ResultCatcher catcher;
281
282  DownloadManager* download_manager = GetDownloadManager();
283  scoped_ptr<content::DownloadTestObserver> download_observer(
284      new content::DownloadTestObserverInProgress(download_manager, 1));
285
286  // The resource's URL on the test server.
287  GURL url = test_server_->GetURL("/text_path.txt");
288
289  // The download's target file path.
290  base::FilePath target_path =
291      downloads_dir_.path().Append(FILE_PATH_LITERAL("download_target.txt"));
292
293  // Set the downloads parameters.
294  content::WebContents* web_contents =
295      browser()->tab_strip_model()->GetActiveWebContents();
296  ASSERT_TRUE(web_contents);
297  scoped_ptr<DownloadUrlParameters> params(
298      DownloadUrlParameters::FromWebContents(web_contents, url));
299  params->set_file_path(target_path);
300
301  // Start download of the URL with a path "/text_path.txt" on the test server.
302  download_manager->DownloadUrl(params.Pass());
303
304  // Wait for the download to start.
305  download_observer->WaitForFinished();
306
307  // There should have been one download.
308  std::vector<DownloadItem*> downloads;
309  download_manager->GetAllDownloads(&downloads);
310  ASSERT_EQ(1u, downloads.size());
311
312  // Cancel and delete the download statred in the test.
313  DeleteDownloadAndWaitForFlush(downloads[0], download_manager);
314
315  // The test extension should not receive any events by now. Send it an event
316  // with MIME type "test/done", so it stops waiting for the events. (If there
317  // was an event with MIME type 'plain/text', |catcher.GetNextResult()| will
318  // fail regardless of the sent event; chrome.test.notifySuccess will not be
319  // called by the extension).
320  SendDoneEvent();
321  EXPECT_TRUE(catcher.GetNextResult());
322}
323
324}  // namespace
325