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