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