1// Copyright 2013 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 "chrome/browser/chromeos/fileapi/external_file_url_request_job.h" 6 7#include "base/bind.h" 8#include "base/files/file_util.h" 9#include "base/memory/ref_counted.h" 10#include "base/memory/scoped_ptr.h" 11#include "base/run_loop.h" 12#include "base/threading/thread.h" 13#include "chrome/browser/chromeos/drive/drive_file_stream_reader.h" 14#include "chrome/browser/chromeos/drive/drive_integration_service.h" 15#include "chrome/browser/chromeos/drive/fake_file_system.h" 16#include "chrome/browser/chromeos/drive/file_system_util.h" 17#include "chrome/browser/chromeos/drive/test_util.h" 18#include "chrome/browser/drive/fake_drive_service.h" 19#include "chrome/browser/drive/test_util.h" 20#include "chrome/browser/prefs/browser_prefs.h" 21#include "chrome/browser/prefs/pref_service_syncable.h" 22#include "chrome/browser/profiles/profile_manager.h" 23#include "chrome/common/url_constants.h" 24#include "chrome/test/base/testing_browser_process.h" 25#include "chrome/test/base/testing_profile.h" 26#include "chrome/test/base/testing_profile_manager.h" 27#include "components/pref_registry/pref_registry_syncable.h" 28#include "components/pref_registry/testing_pref_service_syncable.h" 29#include "content/public/browser/browser_thread.h" 30#include "content/public/test/test_browser_thread_bundle.h" 31#include "content/public/test/test_file_system_options.h" 32#include "google_apis/drive/test_util.h" 33#include "net/base/request_priority.h" 34#include "net/base/test_completion_callback.h" 35#include "net/http/http_byte_range.h" 36#include "net/url_request/redirect_info.h" 37#include "net/url_request/url_request.h" 38#include "net/url_request/url_request_context.h" 39#include "net/url_request/url_request_test_util.h" 40#include "storage/browser/fileapi/external_mount_points.h" 41#include "storage/browser/fileapi/file_system_context.h" 42#include "testing/gtest/include/gtest/gtest.h" 43#include "url/gurl.h" 44 45namespace chromeos { 46namespace { 47 48// A simple URLRequestJobFactory implementation to create 49// ExternalFileURLRequestJob. 50class TestURLRequestJobFactory : public net::URLRequestJobFactory { 51 public: 52 explicit TestURLRequestJobFactory(void* profile_id) 53 : profile_id_(profile_id) {} 54 55 virtual ~TestURLRequestJobFactory() {} 56 57 // net::URLRequestJobFactory override: 58 virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler( 59 const std::string& scheme, 60 net::URLRequest* request, 61 net::NetworkDelegate* network_delegate) const OVERRIDE { 62 return new ExternalFileURLRequestJob( 63 profile_id_, request, network_delegate); 64 } 65 66 virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE { 67 return scheme == chrome::kExternalFileScheme; 68 } 69 70 virtual bool IsHandledURL(const GURL& url) const OVERRIDE { 71 return url.is_valid() && IsHandledProtocol(url.scheme()); 72 } 73 74 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 75 return true; 76 } 77 78 private: 79 void* const profile_id_; 80 DISALLOW_COPY_AND_ASSIGN(TestURLRequestJobFactory); 81}; 82 83class TestDelegate : public net::TestDelegate { 84 public: 85 TestDelegate() {} 86 87 const GURL& redirect_url() const { return redirect_url_; } 88 89 // net::TestDelegate override. 90 virtual void OnReceivedRedirect(net::URLRequest* request, 91 const net::RedirectInfo& redirect_info, 92 bool* defer_redirect) OVERRIDE { 93 redirect_url_ = redirect_info.new_url; 94 net::TestDelegate::OnReceivedRedirect( 95 request, redirect_info, defer_redirect); 96 } 97 98 private: 99 GURL redirect_url_; 100 101 DISALLOW_COPY_AND_ASSIGN(TestDelegate); 102}; 103 104} // namespace 105 106class ExternalFileURLRequestJobTest : public testing::Test { 107 protected: 108 ExternalFileURLRequestJobTest() 109 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), 110 integration_service_factory_callback_(base::Bind( 111 &ExternalFileURLRequestJobTest::CreateDriveIntegrationService, 112 base::Unretained(this))), 113 fake_file_system_(NULL) {} 114 115 virtual ~ExternalFileURLRequestJobTest() {} 116 117 virtual void SetUp() OVERRIDE { 118 // Create a testing profile. 119 profile_manager_.reset( 120 new TestingProfileManager(TestingBrowserProcess::GetGlobal())); 121 ASSERT_TRUE(profile_manager_->SetUp()); 122 Profile* const profile = 123 profile_manager_->CreateTestingProfile("test-user"); 124 125 // Create the drive integration service for the profile. 126 integration_service_factory_scope_.reset( 127 new drive::DriveIntegrationServiceFactory::ScopedFactoryForTest( 128 &integration_service_factory_callback_)); 129 drive::DriveIntegrationServiceFactory::GetForProfile(profile); 130 131 // Create the URL request job factory. 132 test_network_delegate_.reset(new net::TestNetworkDelegate); 133 test_url_request_job_factory_.reset(new TestURLRequestJobFactory(profile)); 134 url_request_context_.reset(new net::URLRequestContext()); 135 url_request_context_->set_job_factory(test_url_request_job_factory_.get()); 136 url_request_context_->set_network_delegate(test_network_delegate_.get()); 137 test_delegate_.reset(new TestDelegate); 138 } 139 140 virtual void TearDown() { profile_manager_.reset(); } 141 142 bool ReadDriveFileSync(const base::FilePath& file_path, 143 std::string* out_content) { 144 scoped_ptr<base::Thread> worker_thread( 145 new base::Thread("ReadDriveFileSync")); 146 if (!worker_thread->Start()) 147 return false; 148 149 scoped_ptr<drive::DriveFileStreamReader> reader( 150 new drive::DriveFileStreamReader( 151 base::Bind(&ExternalFileURLRequestJobTest::GetFileSystem, 152 base::Unretained(this)), 153 worker_thread->message_loop_proxy().get())); 154 int error = net::ERR_FAILED; 155 scoped_ptr<drive::ResourceEntry> entry; 156 { 157 base::RunLoop run_loop; 158 reader->Initialize(file_path, 159 net::HttpByteRange(), 160 google_apis::test_util::CreateQuitCallback( 161 &run_loop, 162 google_apis::test_util::CreateCopyResultCallback( 163 &error, &entry))); 164 run_loop.Run(); 165 } 166 if (error != net::OK || !entry) 167 return false; 168 169 // Read data from the reader. 170 std::string content; 171 if (drive::test_util::ReadAllData(reader.get(), &content) != net::OK) 172 return false; 173 174 if (static_cast<size_t>(entry->file_info().size()) != content.size()) 175 return false; 176 177 *out_content = content; 178 return true; 179 } 180 181 scoped_ptr<net::URLRequestContext> url_request_context_; 182 scoped_ptr<TestDelegate> test_delegate_; 183 184 private: 185 // Create the drive integration service for the |profile| 186 drive::DriveIntegrationService* CreateDriveIntegrationService( 187 Profile* profile) { 188 drive::FakeDriveService* const drive_service = new drive::FakeDriveService; 189 if (!drive::test_util::SetUpTestEntries(drive_service)) 190 return NULL; 191 192 const std::string& drive_mount_name = 193 drive::util::GetDriveMountPointPath(profile).BaseName().AsUTF8Unsafe(); 194 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( 195 drive_mount_name, 196 storage::kFileSystemTypeDrive, 197 storage::FileSystemMountOption(), 198 drive::util::GetDriveMountPointPath(profile)); 199 DCHECK(!fake_file_system_); 200 fake_file_system_ = new drive::test_util::FakeFileSystem(drive_service); 201 if (!drive_cache_dir_.CreateUniqueTempDir()) 202 return NULL; 203 return new drive::DriveIntegrationService(profile, 204 NULL, 205 drive_service, 206 drive_mount_name, 207 drive_cache_dir_.path(), 208 fake_file_system_); 209 } 210 211 drive::FileSystemInterface* GetFileSystem() { return fake_file_system_; } 212 213 content::TestBrowserThreadBundle thread_bundle_; 214 drive::DriveIntegrationServiceFactory::FactoryCallback 215 integration_service_factory_callback_; 216 scoped_ptr<drive::DriveIntegrationServiceFactory::ScopedFactoryForTest> 217 integration_service_factory_scope_; 218 scoped_ptr<drive::DriveIntegrationService> integration_service_; 219 drive::test_util::FakeFileSystem* fake_file_system_; 220 221 scoped_ptr<net::TestNetworkDelegate> test_network_delegate_; 222 scoped_ptr<TestURLRequestJobFactory> test_url_request_job_factory_; 223 224 scoped_ptr<TestingProfileManager> profile_manager_; 225 base::ScopedTempDir drive_cache_dir_; 226 scoped_refptr<storage::FileSystemContext> file_system_context_; 227}; 228 229TEST_F(ExternalFileURLRequestJobTest, NonGetMethod) { 230 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 231 GURL("externalfile:drive-test-user-hash/root/File 1.txt"), 232 net::DEFAULT_PRIORITY, 233 test_delegate_.get(), 234 NULL)); 235 request->set_method("POST"); // Set non "GET" method. 236 request->Start(); 237 238 base::RunLoop().Run(); 239 240 EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); 241 EXPECT_EQ(net::ERR_METHOD_NOT_SUPPORTED, request->status().error()); 242} 243 244TEST_F(ExternalFileURLRequestJobTest, RegularFile) { 245 const GURL kTestUrl("externalfile:drive-test-user-hash/root/File 1.txt"); 246 const base::FilePath kTestFilePath("drive/root/File 1.txt"); 247 248 // For the first time, the file should be fetched from the server. 249 { 250 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 251 kTestUrl, net::DEFAULT_PRIORITY, test_delegate_.get(), NULL)); 252 request->Start(); 253 254 base::RunLoop().Run(); 255 256 EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status()); 257 // It looks weird, but the mime type for the "File 1.txt" is "audio/mpeg" 258 // on the server. 259 std::string mime_type; 260 request->GetMimeType(&mime_type); 261 EXPECT_EQ("audio/mpeg", mime_type); 262 263 // Reading file must be done after |request| runs, otherwise 264 // it'll create a local cache file, and we cannot test correctly. 265 std::string expected_data; 266 ASSERT_TRUE(ReadDriveFileSync(kTestFilePath, &expected_data)); 267 EXPECT_EQ(expected_data, test_delegate_->data_received()); 268 } 269 270 // For the second time, the locally cached file should be used. 271 // The caching emulation is done by FakeFileSystem. 272 { 273 test_delegate_.reset(new TestDelegate); 274 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 275 GURL("externalfile:drive-test-user-hash/root/File 1.txt"), 276 net::DEFAULT_PRIORITY, 277 test_delegate_.get(), 278 NULL)); 279 request->Start(); 280 281 base::RunLoop().Run(); 282 283 EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status()); 284 std::string mime_type; 285 request->GetMimeType(&mime_type); 286 EXPECT_EQ("audio/mpeg", mime_type); 287 288 std::string expected_data; 289 ASSERT_TRUE(ReadDriveFileSync(kTestFilePath, &expected_data)); 290 EXPECT_EQ(expected_data, test_delegate_->data_received()); 291 } 292} 293 294TEST_F(ExternalFileURLRequestJobTest, HostedDocument) { 295 // Open a gdoc file. 296 test_delegate_->set_quit_on_redirect(true); 297 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 298 GURL( 299 "externalfile:drive-test-user-hash/root/Document 1 " 300 "excludeDir-test.gdoc"), 301 net::DEFAULT_PRIORITY, 302 test_delegate_.get(), 303 NULL)); 304 request->Start(); 305 306 base::RunLoop().Run(); 307 308 EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status()); 309 // Make sure that a hosted document triggers redirection. 310 EXPECT_TRUE(request->is_redirecting()); 311 EXPECT_TRUE(test_delegate_->redirect_url().is_valid()); 312} 313 314TEST_F(ExternalFileURLRequestJobTest, RootDirectory) { 315 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 316 GURL("externalfile:drive-test-user-hash/root"), 317 net::DEFAULT_PRIORITY, 318 test_delegate_.get(), 319 NULL)); 320 request->Start(); 321 322 base::RunLoop().Run(); 323 324 EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); 325 EXPECT_EQ(net::ERR_FAILED, request->status().error()); 326} 327 328TEST_F(ExternalFileURLRequestJobTest, Directory) { 329 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 330 GURL("externalfile:drive-test-user-hash/root/Directory 1"), 331 net::DEFAULT_PRIORITY, 332 test_delegate_.get(), 333 NULL)); 334 request->Start(); 335 336 base::RunLoop().Run(); 337 338 EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); 339 EXPECT_EQ(net::ERR_FAILED, request->status().error()); 340} 341 342TEST_F(ExternalFileURLRequestJobTest, NonExistingFile) { 343 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 344 GURL("externalfile:drive-test-user-hash/root/non-existing-file.txt"), 345 net::DEFAULT_PRIORITY, 346 test_delegate_.get(), 347 NULL)); 348 request->Start(); 349 350 base::RunLoop().Run(); 351 352 EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); 353 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request->status().error()); 354} 355 356TEST_F(ExternalFileURLRequestJobTest, WrongFormat) { 357 scoped_ptr<net::URLRequest> request( 358 url_request_context_->CreateRequest(GURL("externalfile:"), 359 net::DEFAULT_PRIORITY, 360 test_delegate_.get(), 361 NULL)); 362 request->Start(); 363 364 base::RunLoop().Run(); 365 366 EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); 367 EXPECT_EQ(net::ERR_INVALID_URL, request->status().error()); 368} 369 370TEST_F(ExternalFileURLRequestJobTest, Cancel) { 371 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 372 GURL("externalfile:drive-test-user-hash/root/File 1.txt"), 373 net::DEFAULT_PRIORITY, 374 test_delegate_.get(), 375 NULL)); 376 377 // Start the request, and cancel it immediately after it. 378 request->Start(); 379 request->Cancel(); 380 381 base::RunLoop().Run(); 382 383 EXPECT_EQ(net::URLRequestStatus::CANCELED, request->status().status()); 384} 385 386TEST_F(ExternalFileURLRequestJobTest, RangeHeader) { 387 const GURL kTestUrl("externalfile:drive-test-user-hash/root/File 1.txt"); 388 const base::FilePath kTestFilePath("drive/root/File 1.txt"); 389 390 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 391 kTestUrl, net::DEFAULT_PRIORITY, test_delegate_.get(), NULL)); 392 393 // Set range header. 394 request->SetExtraRequestHeaderByName( 395 "Range", "bytes=3-5", false /* overwrite */); 396 request->Start(); 397 398 base::RunLoop().Run(); 399 400 EXPECT_EQ(net::URLRequestStatus::SUCCESS, request->status().status()); 401 402 // Reading file must be done after |request| runs, otherwise 403 // it'll create a local cache file, and we cannot test correctly. 404 std::string expected_data; 405 ASSERT_TRUE(ReadDriveFileSync(kTestFilePath, &expected_data)); 406 EXPECT_EQ(expected_data.substr(3, 3), test_delegate_->data_received()); 407} 408 409TEST_F(ExternalFileURLRequestJobTest, WrongRangeHeader) { 410 const GURL kTestUrl("externalfile:drive-test-user-hash/root/File 1.txt"); 411 412 scoped_ptr<net::URLRequest> request(url_request_context_->CreateRequest( 413 kTestUrl, net::DEFAULT_PRIORITY, test_delegate_.get(), NULL)); 414 415 // Set range header. 416 request->SetExtraRequestHeaderByName( 417 "Range", "Wrong Range Header Value", false /* overwrite */); 418 request->Start(); 419 420 base::RunLoop().Run(); 421 422 EXPECT_EQ(net::URLRequestStatus::FAILED, request->status().status()); 423 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, request->status().error()); 424} 425 426} // namespace chromeos 427