• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4 
5 #include "include/cef_scheme.h"
6 #include "include/wrapper/cef_closure_task.h"
7 #include "include/wrapper/cef_scoped_temp_dir.h"
8 #include "tests/ceftests/test_handler.h"
9 #include "tests/ceftests/test_util.h"
10 #include "tests/gtest/include/gtest/gtest.h"
11 #include "tests/shared/browser/file_util.h"
12 
13 namespace {
14 
15 const char kTestDomain[] = "test-download.com";
16 const char kTestStartUrl[] = "http://test-download.com/test.html";
17 const char kTestDownloadUrl[] = "http://test-download.com/download.txt";
18 const char kTestNavUrl[] = "http://test-download-nav.com/nav.html";
19 const char kTestFileName[] = "download_test.txt";
20 const char kTestContentDisposition[] =
21     "attachment; filename=\"download_test.txt\"";
22 const char kTestMimeType[] = "text/plain";
23 const char kTestContent[] = "Download test text";
24 
25 typedef base::Callback<void(const base::Closure& /*callback*/)> DelayCallback;
26 
27 class DownloadSchemeHandler : public CefResourceHandler {
28  public:
DownloadSchemeHandler(const DelayCallback & delay_callback,TrackCallback * got_download_request)29   DownloadSchemeHandler(const DelayCallback& delay_callback,
30                         TrackCallback* got_download_request)
31       : delay_callback_(delay_callback),
32         got_download_request_(got_download_request),
33         should_delay_(false),
34         offset_(0) {}
35 
Open(CefRefPtr<CefRequest> request,bool & handle_request,CefRefPtr<CefCallback> callback)36   bool Open(CefRefPtr<CefRequest> request,
37             bool& handle_request,
38             CefRefPtr<CefCallback> callback) override {
39     EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
40 
41     std::string url = request->GetURL();
42     if (url == kTestDownloadUrl) {
43       got_download_request_->yes();
44       content_ = kTestContent;
45       mime_type_ = kTestMimeType;
46       content_disposition_ = kTestContentDisposition;
47       should_delay_ = true;
48     } else {
49       EXPECT_TRUE(false);  // Not reached.
50 
51       // Cancel immediately.
52       handle_request = true;
53       return false;
54     }
55 
56     // Continue immediately.
57     handle_request = true;
58     return true;
59   }
60 
GetResponseHeaders(CefRefPtr<CefResponse> response,int64 & response_length,CefString & redirectUrl)61   void GetResponseHeaders(CefRefPtr<CefResponse> response,
62                           int64& response_length,
63                           CefString& redirectUrl) override {
64     response_length = content_.size();
65 
66     response->SetStatus(200);
67     response->SetMimeType(mime_type_);
68 
69     if (!content_disposition_.empty()) {
70       CefResponse::HeaderMap headerMap;
71       response->GetHeaderMap(headerMap);
72       headerMap.insert(
73           std::make_pair("Content-Disposition", content_disposition_));
74       response->SetHeaderMap(headerMap);
75     }
76   }
77 
Read(void * data_out,int bytes_to_read,int & bytes_read,CefRefPtr<CefResourceReadCallback> callback)78   bool Read(void* data_out,
79             int bytes_to_read,
80             int& bytes_read,
81             CefRefPtr<CefResourceReadCallback> callback) override {
82     EXPECT_FALSE(CefCurrentlyOn(TID_UI) || CefCurrentlyOn(TID_IO));
83 
84     bytes_read = 0;
85 
86     if (should_delay_ && !delay_callback_.is_null()) {
87       // Delay the download response a single time.
88       delay_callback_.Run(base::Bind(&DownloadSchemeHandler::ContinueRead, this,
89                                      data_out, bytes_to_read, callback));
90       delay_callback_.Reset();
91       return true;
92     }
93 
94     return DoRead(data_out, bytes_to_read, bytes_read);
95   }
96 
Cancel()97   void Cancel() override {}
98 
99  private:
ContinueRead(void * data_out,int bytes_to_read,CefRefPtr<CefResourceReadCallback> callback)100   void ContinueRead(void* data_out,
101                     int bytes_to_read,
102                     CefRefPtr<CefResourceReadCallback> callback) {
103     int bytes_read = 0;
104     DoRead(data_out, bytes_to_read, bytes_read);
105     callback->Continue(bytes_read);
106   }
107 
DoRead(void * data_out,int bytes_to_read,int & bytes_read)108   bool DoRead(void* data_out, int bytes_to_read, int& bytes_read) {
109     bool has_data = false;
110     size_t size = content_.size();
111     if (offset_ < size) {
112       int transfer_size =
113           std::min(bytes_to_read, static_cast<int>(size - offset_));
114       memcpy(data_out, content_.c_str() + offset_, transfer_size);
115       offset_ += transfer_size;
116 
117       bytes_read = transfer_size;
118       has_data = true;
119     }
120 
121     return has_data;
122   }
123 
124   DelayCallback delay_callback_;
125   TrackCallback* got_download_request_;
126   bool should_delay_;
127   std::string content_;
128   std::string mime_type_;
129   std::string content_disposition_;
130   size_t offset_;
131   CefRefPtr<CefResourceReadCallback> read_callback_;
132 
133   IMPLEMENT_REFCOUNTING(DownloadSchemeHandler);
134   DISALLOW_COPY_AND_ASSIGN(DownloadSchemeHandler);
135 };
136 
137 class DownloadSchemeHandlerFactory : public CefSchemeHandlerFactory {
138  public:
DownloadSchemeHandlerFactory(const DelayCallback & delay_callback,TrackCallback * got_download_request)139   DownloadSchemeHandlerFactory(const DelayCallback& delay_callback,
140                                TrackCallback* got_download_request)
141       : delay_callback_(delay_callback),
142         got_download_request_(got_download_request) {}
143 
Create(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,const CefString & scheme_name,CefRefPtr<CefRequest> request)144   CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser,
145                                        CefRefPtr<CefFrame> frame,
146                                        const CefString& scheme_name,
147                                        CefRefPtr<CefRequest> request) override {
148     return new DownloadSchemeHandler(delay_callback_, got_download_request_);
149   }
150 
151  private:
152   DelayCallback delay_callback_;
153   TrackCallback* got_download_request_;
154 
155   IMPLEMENT_REFCOUNTING(DownloadSchemeHandlerFactory);
156   DISALLOW_COPY_AND_ASSIGN(DownloadSchemeHandlerFactory);
157 };
158 
159 class DownloadTestHandler : public TestHandler {
160  public:
161   enum TestMode {
162     PROGAMMATIC,
163     NAVIGATED,
164     PENDING,
165     CLICKED,
166     CLICKED_REJECTED,
167   };
168 
DownloadTestHandler(TestMode test_mode,TestRequestContextMode rc_mode,const std::string & rc_cache_path)169   DownloadTestHandler(TestMode test_mode,
170                       TestRequestContextMode rc_mode,
171                       const std::string& rc_cache_path)
172       : test_mode_(test_mode),
173         rc_mode_(rc_mode),
174         rc_cache_path_(rc_cache_path),
175         download_id_(0),
176         verified_results_(false) {}
177 
is_clicked() const178   bool is_clicked() const {
179     return test_mode_ == CLICKED || test_mode_ == CLICKED_REJECTED;
180   }
181 
RunTest()182   void RunTest() override {
183     DelayCallback delay_callback;
184     if (test_mode_ == NAVIGATED || test_mode_ == PENDING)
185       delay_callback = base::Bind(&DownloadTestHandler::OnDelayCallback, this);
186 
187     CefRefPtr<CefSchemeHandlerFactory> scheme_factory =
188         new DownloadSchemeHandlerFactory(delay_callback,
189                                          &got_download_request_);
190 
191     CefRefPtr<CefRequestContext> request_context =
192         CreateTestRequestContext(rc_mode_, rc_cache_path_);
193     if (request_context) {
194       request_context->RegisterSchemeHandlerFactory("http", kTestDomain,
195                                                     scheme_factory);
196     } else {
197       CefRegisterSchemeHandlerFactory("http", kTestDomain, scheme_factory);
198     }
199 
200     if (test_mode_ != CLICKED_REJECTED) {
201       // Create a new temporary directory.
202       EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
203       test_path_ =
204           client::file_util::JoinPath(temp_dir_.GetPath(), kTestFileName);
205     }
206 
207     if (test_mode_ == NAVIGATED) {
208       // Add the resource that we'll navigate to.
209       AddResource(kTestNavUrl, "<html><body>Navigated</body></html>",
210                   "text/html");
211     }
212 
213     if (is_clicked()) {
214       std::string url;
215       if (test_mode_ == CLICKED) {
216         url = kTestDownloadUrl;
217       } else if (test_mode_ == CLICKED_REJECTED) {
218         url = "invalid:foo@example.com";
219       } else {
220         EXPECT_TRUE(false);  // Not reached.
221       }
222       AddResource(
223           kTestStartUrl,
224           "<html><body><a href=\"" + url + "\">CLICK ME</a></body></html>",
225           "text/html");
226     } else {
227       AddResource(kTestStartUrl, "<html><body>Download Test</body></html>",
228                   "text/html");
229     }
230 
231     // Create the browser
232     CreateBrowser(kTestStartUrl, request_context);
233 
234     // Time out the test after a reasonable period of time.
235     SetTestTimeout();
236   }
237 
OnLoadEnd(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int httpStatusCode)238   void OnLoadEnd(CefRefPtr<CefBrowser> browser,
239                  CefRefPtr<CefFrame> frame,
240                  int httpStatusCode) override {
241     const std::string& url = frame->GetURL().ToString();
242     if (url == kTestNavUrl) {
243       got_nav_load_.yes();
244       ContinueNavigatedIfReady();
245       return;
246     }
247 
248     if (is_clicked()) {
249       // Begin the download by clicking a link.
250       // ALT key will trigger download of custom protocol links.
251       SendClick(browser,
252                 test_mode_ == CLICKED_REJECTED ? EVENTFLAG_ALT_DOWN : 0);
253 
254       if (test_mode_ == CLICKED_REJECTED) {
255         // Destroy the test after a bit because there will be no further
256         // callbacks.
257         CefPostDelayedTask(
258             TID_UI, base::Bind(&DownloadTestHandler::DestroyTest, this), 200);
259       }
260     } else {
261       // Begin the download progammatically.
262       browser->GetHost()->StartDownload(kTestDownloadUrl);
263     }
264   }
265 
266   // Callback from the scheme handler when the download request is delayed.
OnDelayCallback(const base::Closure & callback)267   void OnDelayCallback(const base::Closure& callback) {
268     if (!CefCurrentlyOn(TID_UI)) {
269       CefPostTask(TID_UI, base::Bind(&DownloadTestHandler::OnDelayCallback,
270                                      this, callback));
271       return;
272     }
273 
274     got_delay_callback_.yes();
275 
276     if (test_mode_ == NAVIGATED) {
277       delay_callback_ = callback;
278       ContinueNavigatedIfReady();
279     } else if (test_mode_ == PENDING) {
280       ContinuePendingIfReady();
281     } else {
282       EXPECT_TRUE(false);  // Not reached.
283     }
284   }
285 
ContinueNavigatedIfReady()286   void ContinueNavigatedIfReady() {
287     EXPECT_EQ(test_mode_, NAVIGATED);
288     if (got_delay_callback_ && got_nav_load_) {
289       EXPECT_FALSE(delay_callback_.is_null());
290       delay_callback_.Run();
291       delay_callback_.Reset();
292     }
293   }
294 
ContinuePendingIfReady()295   void ContinuePendingIfReady() {
296     EXPECT_EQ(test_mode_, PENDING);
297     if (got_delay_callback_ && got_on_before_download_ &&
298         got_on_download_updated_) {
299       // Destroy the test without waiting for the download to complete.
300       DestroyTest();
301     }
302   }
303 
OnBeforeDownload(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDownloadItem> download_item,const CefString & suggested_name,CefRefPtr<CefBeforeDownloadCallback> callback)304   void OnBeforeDownload(
305       CefRefPtr<CefBrowser> browser,
306       CefRefPtr<CefDownloadItem> download_item,
307       const CefString& suggested_name,
308       CefRefPtr<CefBeforeDownloadCallback> callback) override {
309     EXPECT_TRUE(CefCurrentlyOn(TID_UI));
310     EXPECT_FALSE(got_on_before_download_);
311 
312     got_on_before_download_.yes();
313 
314     EXPECT_TRUE(browser->IsSame(GetBrowser()));
315     EXPECT_STREQ(kTestFileName, suggested_name.ToString().c_str());
316     EXPECT_TRUE(download_item.get());
317     EXPECT_TRUE(callback.get());
318 
319     download_id_ = download_item->GetId();
320     EXPECT_LT(0U, download_id_);
321 
322     EXPECT_TRUE(download_item->IsValid());
323     EXPECT_TRUE(download_item->IsInProgress());
324     EXPECT_FALSE(download_item->IsComplete());
325     EXPECT_FALSE(download_item->IsCanceled());
326     EXPECT_EQ(static_cast<int64>(sizeof(kTestContent) - 1),
327               download_item->GetTotalBytes());
328     EXPECT_EQ(0UL, download_item->GetFullPath().length());
329     EXPECT_STREQ(kTestDownloadUrl, download_item->GetURL().ToString().c_str());
330     EXPECT_EQ(0UL, download_item->GetSuggestedFileName().length());
331     EXPECT_STREQ(kTestContentDisposition,
332                  download_item->GetContentDisposition().ToString().c_str());
333     EXPECT_STREQ(kTestMimeType,
334                  download_item->GetMimeType().ToString().c_str());
335 
336     callback->Continue(test_path_, false);
337 
338     if (test_mode_ == NAVIGATED) {
339       CefRefPtr<CefFrame> main_frame = browser->GetMainFrame();
340       EXPECT_TRUE(main_frame->IsMain());
341       main_frame->LoadURL(kTestNavUrl);
342     } else if (test_mode_ == PENDING) {
343       ContinuePendingIfReady();
344     }
345   }
346 
OnDownloadUpdated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefDownloadItem> download_item,CefRefPtr<CefDownloadItemCallback> callback)347   void OnDownloadUpdated(CefRefPtr<CefBrowser> browser,
348                          CefRefPtr<CefDownloadItem> download_item,
349                          CefRefPtr<CefDownloadItemCallback> callback) override {
350     EXPECT_TRUE(CefCurrentlyOn(TID_UI));
351 
352     if (destroyed_)
353       return;
354 
355     got_on_download_updated_.yes();
356 
357     EXPECT_TRUE(browser->IsSame(GetBrowser()));
358     EXPECT_TRUE(download_item.get());
359     EXPECT_TRUE(callback.get());
360 
361     if (got_on_before_download_) {
362       EXPECT_EQ(download_id_, download_item->GetId());
363     }
364 
365     EXPECT_LE(0LL, download_item->GetCurrentSpeed());
366     EXPECT_LE(0, download_item->GetPercentComplete());
367 
368     EXPECT_TRUE(download_item->IsValid());
369     EXPECT_FALSE(download_item->IsCanceled());
370     EXPECT_STREQ(kTestDownloadUrl, download_item->GetURL().ToString().c_str());
371     EXPECT_STREQ(kTestContentDisposition,
372                  download_item->GetContentDisposition().ToString().c_str());
373     EXPECT_STREQ(kTestMimeType,
374                  download_item->GetMimeType().ToString().c_str());
375 
376     std::string full_path = download_item->GetFullPath();
377     if (!full_path.empty()) {
378       got_full_path_.yes();
379       EXPECT_STREQ(test_path_.c_str(), full_path.c_str());
380     }
381 
382     if (download_item->IsComplete()) {
383       got_download_complete_.yes();
384 
385       EXPECT_FALSE(download_item->IsInProgress());
386       EXPECT_EQ(100, download_item->GetPercentComplete());
387       EXPECT_EQ(static_cast<int64>(sizeof(kTestContent) - 1),
388                 download_item->GetReceivedBytes());
389       EXPECT_EQ(static_cast<int64>(sizeof(kTestContent) - 1),
390                 download_item->GetTotalBytes());
391 
392       DestroyTest();
393     } else {
394       EXPECT_TRUE(download_item->IsInProgress());
395       EXPECT_LE(0LL, download_item->GetReceivedBytes());
396     }
397 
398     if (test_mode_ == PENDING) {
399       download_item_callback_ = callback;
400       ContinuePendingIfReady();
401     }
402   }
403 
VerifyResultsOnFileThread()404   void VerifyResultsOnFileThread() {
405     EXPECT_TRUE(CefCurrentlyOn(TID_FILE_USER_VISIBLE));
406 
407     if (test_mode_ != PENDING) {
408       // Verify the file contents.
409       std::string contents;
410       EXPECT_TRUE(client::file_util::ReadFileToString(test_path_, &contents));
411       EXPECT_STREQ(kTestContent, contents.c_str());
412     }
413 
414     EXPECT_TRUE(temp_dir_.Delete());
415     EXPECT_TRUE(temp_dir_.IsEmpty());
416 
417     CefPostTask(TID_UI, base::Bind(&DownloadTestHandler::DestroyTest, this));
418   }
419 
DestroyTest()420   void DestroyTest() override {
421     if (!verified_results_ && !temp_dir_.IsEmpty()) {
422       // Avoid an endless failure loop.
423       verified_results_ = true;
424       // Clean up temp_dir_ on the FILE thread before destroying the test.
425       CefPostTask(
426           TID_FILE_USER_VISIBLE,
427           base::Bind(&DownloadTestHandler::VerifyResultsOnFileThread, this));
428       return;
429     }
430 
431     destroyed_ = true;
432 
433     if (download_item_callback_) {
434       // Cancel the pending download to avoid leaking request objects.
435       download_item_callback_->Cancel();
436       download_item_callback_ = nullptr;
437     }
438 
439     if (request_context_) {
440       request_context_->RegisterSchemeHandlerFactory("http", kTestDomain,
441                                                      nullptr);
442       request_context_ = nullptr;
443     } else {
444       CefRegisterSchemeHandlerFactory("http", kTestDomain, nullptr);
445     }
446 
447     if (test_mode_ == CLICKED_REJECTED) {
448       EXPECT_FALSE(got_download_request_);
449       EXPECT_FALSE(got_on_before_download_);
450       EXPECT_FALSE(got_on_download_updated_);
451     } else {
452       EXPECT_TRUE(got_download_request_);
453       EXPECT_TRUE(got_on_before_download_);
454       EXPECT_TRUE(got_on_download_updated_);
455     }
456 
457     if (test_mode_ == NAVIGATED)
458       EXPECT_TRUE(got_nav_load_);
459     else
460       EXPECT_FALSE(got_nav_load_);
461 
462     if (test_mode_ == PENDING || test_mode_ == CLICKED_REJECTED) {
463       EXPECT_FALSE(got_download_complete_);
464       EXPECT_FALSE(got_full_path_);
465     } else {
466       EXPECT_TRUE(got_download_complete_);
467       EXPECT_TRUE(got_full_path_);
468     }
469 
470     TestHandler::DestroyTest();
471   }
472 
473  private:
SendClick(CefRefPtr<CefBrowser> browser,uint32_t modifiers)474   void SendClick(CefRefPtr<CefBrowser> browser, uint32_t modifiers) {
475     EXPECT_TRUE(is_clicked());
476     CefMouseEvent mouse_event;
477     mouse_event.x = 20;
478     mouse_event.y = 20;
479     mouse_event.modifiers = modifiers;
480     browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, false, 1);
481     browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, true, 1);
482   }
483 
484   const TestMode test_mode_;
485   const TestRequestContextMode rc_mode_;
486   const std::string rc_cache_path_;
487 
488   CefRefPtr<CefRequestContext> request_context_;
489 
490   // Used with NAVIGATED and PENDING test modes.
491   base::Closure delay_callback_;
492 
493   // Used with PENDING test mode.
494   CefRefPtr<CefDownloadItemCallback> download_item_callback_;
495 
496   CefScopedTempDir temp_dir_;
497   std::string test_path_;
498   uint32 download_id_;
499   bool verified_results_;
500   bool destroyed_ = false;
501 
502   TrackCallback got_download_request_;
503   TrackCallback got_on_before_download_;
504   TrackCallback got_on_download_updated_;
505   TrackCallback got_full_path_;
506   TrackCallback got_download_complete_;
507   TrackCallback got_delay_callback_;
508   TrackCallback got_nav_load_;
509 
510   IMPLEMENT_REFCOUNTING(DownloadTestHandler);
511 };
512 
513 }  // namespace
514 
515 #define DOWNLOAD_TEST_GROUP(test_name, test_mode) \
516   RC_TEST_GROUP_ALL(DownloadTest, test_name, DownloadTestHandler, test_mode)
517 
518 // Test a programmatic download.
519 DOWNLOAD_TEST_GROUP(Programmatic, PROGAMMATIC)
520 
521 // Test a clicked download.
522 DOWNLOAD_TEST_GROUP(Clicked, CLICKED)
523 
524 // Test a clicked download where the protocol is invalid and therefore rejected.
525 // There will be no resulting CefDownloadHandler callbacks.
526 DOWNLOAD_TEST_GROUP(ClickedRejected, CLICKED_REJECTED)
527 
528 // Test where the download completes after cross-origin navigation.
529 DOWNLOAD_TEST_GROUP(Navigated, NAVIGATED)
530 
531 // Test where the download is still pending when the browser is destroyed.
532 DOWNLOAD_TEST_GROUP(Pending, PENDING)
533