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