1 // Copyright (c) 2012 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 <set> 6 #include <string> 7 8 #include "base/bind.h" 9 #include "base/files/file_util.h" 10 #include "base/files/scoped_temp_dir.h" 11 #include "base/format_macros.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/message_loop/message_loop_proxy.h" 14 #include "base/strings/stringprintf.h" 15 #include "base/time/time.h" 16 #include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h" 17 #include "chrome/browser/media_galleries/fileapi/native_media_file_util.h" 18 #include "content/public/test/mock_special_storage_policy.h" 19 #include "content/public/test/test_browser_thread.h" 20 #include "content/public/test/test_file_system_options.h" 21 #include "storage/browser/fileapi/external_mount_points.h" 22 #include "storage/browser/fileapi/file_system_backend.h" 23 #include "storage/browser/fileapi/file_system_context.h" 24 #include "storage/browser/fileapi/file_system_operation_runner.h" 25 #include "storage/browser/fileapi/file_system_url.h" 26 #include "storage/browser/fileapi/isolated_context.h" 27 #include "storage/browser/fileapi/native_file_util.h" 28 #include "testing/gtest/include/gtest/gtest.h" 29 30 #define FPL(x) FILE_PATH_LITERAL(x) 31 32 using storage::FileSystemOperation; 33 using storage::FileSystemURL; 34 35 namespace { 36 37 typedef FileSystemOperation::FileEntryList FileEntryList; 38 39 struct FilteringTestCase { 40 const base::FilePath::CharType* path; 41 bool is_directory; 42 bool visible; 43 bool media_file; 44 const char* content; 45 }; 46 47 const FilteringTestCase kFilteringTestCases[] = { 48 // Directory should always be visible. 49 { FPL("hoge"), true, true, false, NULL }, 50 { FPL("fuga.jpg"), true, true, false, NULL }, 51 { FPL("piyo.txt"), true, true, false, NULL }, 52 { FPL("moga.cod"), true, true, false, NULL }, 53 54 // File should be visible if it's a supported media file. 55 // File without extension. 56 { FPL("foo"), false, false, false, "abc" }, 57 // Supported media file. 58 { FPL("bar.jpg"), false, true, true, "\xFF\xD8\xFF" }, 59 // Unsupported masquerading file. 60 { FPL("sna.jpg"), false, true, false, "abc" }, 61 // Non-media file. 62 { FPL("baz.txt"), false, false, false, "abc" }, 63 // Unsupported media file. 64 { FPL("foobar.cod"), false, false, false, "abc" }, 65 }; 66 67 void ExpectEqHelper(const std::string& test_name, 68 base::File::Error expected, 69 base::File::Error actual) { 70 EXPECT_EQ(expected, actual) << test_name; 71 } 72 73 void ExpectMetadataEqHelper(const std::string& test_name, 74 base::File::Error expected, 75 bool expected_is_directory, 76 base::File::Error actual, 77 const base::File::Info& file_info) { 78 EXPECT_EQ(expected, actual) << test_name; 79 if (actual == base::File::FILE_OK) 80 EXPECT_EQ(expected_is_directory, file_info.is_directory) << test_name; 81 } 82 83 void DidReadDirectory(std::set<base::FilePath::StringType>* content, 84 bool* completed, 85 base::File::Error error, 86 const FileEntryList& file_list, 87 bool has_more) { 88 EXPECT_TRUE(!*completed); 89 *completed = !has_more; 90 for (FileEntryList::const_iterator itr = file_list.begin(); 91 itr != file_list.end(); ++itr) 92 EXPECT_TRUE(content->insert(itr->name).second); 93 } 94 95 void PopulateDirectoryWithTestCases(const base::FilePath& dir, 96 const FilteringTestCase* test_cases, 97 size_t n) { 98 for (size_t i = 0; i < n; ++i) { 99 base::FilePath path = dir.Append(test_cases[i].path); 100 if (test_cases[i].is_directory) { 101 ASSERT_TRUE(base::CreateDirectory(path)); 102 } else { 103 ASSERT_TRUE(test_cases[i].content != NULL); 104 int len = strlen(test_cases[i].content); 105 ASSERT_EQ(len, base::WriteFile(path, test_cases[i].content, len)); 106 } 107 } 108 } 109 110 } // namespace 111 112 class NativeMediaFileUtilTest : public testing::Test { 113 public: 114 NativeMediaFileUtilTest() 115 : io_thread_(content::BrowserThread::IO, &message_loop_) { 116 } 117 118 virtual void SetUp() { 119 ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); 120 ASSERT_TRUE(base::CreateDirectory(root_path())); 121 122 scoped_refptr<storage::SpecialStoragePolicy> storage_policy = 123 new content::MockSpecialStoragePolicy(); 124 125 ScopedVector<storage::FileSystemBackend> additional_providers; 126 additional_providers.push_back(new MediaFileSystemBackend( 127 data_dir_.path(), base::MessageLoopProxy::current().get())); 128 129 file_system_context_ = new storage::FileSystemContext( 130 base::MessageLoopProxy::current().get(), 131 base::MessageLoopProxy::current().get(), 132 storage::ExternalMountPoints::CreateRefCounted().get(), 133 storage_policy.get(), 134 NULL, 135 additional_providers.Pass(), 136 std::vector<storage::URLRequestAutoMountHandler>(), 137 data_dir_.path(), 138 content::CreateAllowFileAccessOptions()); 139 140 filesystem_id_ = isolated_context()->RegisterFileSystemForPath( 141 storage::kFileSystemTypeNativeMedia, std::string(), root_path(), NULL); 142 143 isolated_context()->AddReference(filesystem_id_); 144 } 145 146 virtual void TearDown() { 147 isolated_context()->RemoveReference(filesystem_id_); 148 file_system_context_ = NULL; 149 } 150 151 protected: 152 storage::FileSystemContext* file_system_context() { 153 return file_system_context_.get(); 154 } 155 156 FileSystemURL CreateURL(const base::FilePath::CharType* test_case_path) { 157 return file_system_context_->CreateCrackedFileSystemURL( 158 origin(), 159 storage::kFileSystemTypeIsolated, 160 GetVirtualPath(test_case_path)); 161 } 162 163 storage::IsolatedContext* isolated_context() { 164 return storage::IsolatedContext::GetInstance(); 165 } 166 167 base::FilePath root_path() { 168 return data_dir_.path().Append(FPL("Media Directory")); 169 } 170 171 base::FilePath GetVirtualPath( 172 const base::FilePath::CharType* test_case_path) { 173 return base::FilePath::FromUTF8Unsafe(filesystem_id_). 174 Append(FPL("Media Directory")). 175 Append(base::FilePath(test_case_path)); 176 } 177 178 GURL origin() { 179 return GURL("http://example.com"); 180 } 181 182 storage::FileSystemType type() { return storage::kFileSystemTypeNativeMedia; } 183 184 storage::FileSystemOperationRunner* operation_runner() { 185 return file_system_context_->operation_runner(); 186 } 187 188 private: 189 base::MessageLoop message_loop_; 190 content::TestBrowserThread io_thread_; 191 192 base::ScopedTempDir data_dir_; 193 scoped_refptr<storage::FileSystemContext> file_system_context_; 194 195 std::string filesystem_id_; 196 197 DISALLOW_COPY_AND_ASSIGN(NativeMediaFileUtilTest); 198 }; 199 200 TEST_F(NativeMediaFileUtilTest, DirectoryExistsAndFileExistsFiltering) { 201 PopulateDirectoryWithTestCases(root_path(), 202 kFilteringTestCases, 203 arraysize(kFilteringTestCases)); 204 205 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 206 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 207 208 base::File::Error expectation = 209 kFilteringTestCases[i].visible ? 210 base::File::FILE_OK : 211 base::File::FILE_ERROR_NOT_FOUND; 212 213 std::string test_name = 214 base::StringPrintf("DirectoryExistsAndFileExistsFiltering %" PRIuS, i); 215 if (kFilteringTestCases[i].is_directory) { 216 operation_runner()->DirectoryExists( 217 url, base::Bind(&ExpectEqHelper, test_name, expectation)); 218 } else { 219 operation_runner()->FileExists( 220 url, base::Bind(&ExpectEqHelper, test_name, expectation)); 221 } 222 base::MessageLoop::current()->RunUntilIdle(); 223 } 224 } 225 226 TEST_F(NativeMediaFileUtilTest, ReadDirectoryFiltering) { 227 PopulateDirectoryWithTestCases(root_path(), 228 kFilteringTestCases, 229 arraysize(kFilteringTestCases)); 230 231 std::set<base::FilePath::StringType> content; 232 FileSystemURL url = CreateURL(FPL("")); 233 bool completed = false; 234 operation_runner()->ReadDirectory( 235 url, base::Bind(&DidReadDirectory, &content, &completed)); 236 base::MessageLoop::current()->RunUntilIdle(); 237 EXPECT_TRUE(completed); 238 EXPECT_EQ(6u, content.size()); 239 240 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 241 base::FilePath::StringType name = 242 base::FilePath(kFilteringTestCases[i].path).BaseName().value(); 243 std::set<base::FilePath::StringType>::const_iterator found = 244 content.find(name); 245 EXPECT_EQ(kFilteringTestCases[i].visible, found != content.end()); 246 } 247 } 248 249 TEST_F(NativeMediaFileUtilTest, CreateDirectoryFiltering) { 250 // Run the loop twice. The second loop attempts to create directories that are 251 // pre-existing. Though the result should be the same. 252 for (int loop_count = 0; loop_count < 2; ++loop_count) { 253 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 254 if (kFilteringTestCases[i].is_directory) { 255 FileSystemURL root_url = CreateURL(FPL("")); 256 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 257 258 std::string test_name = base::StringPrintf( 259 "CreateFileAndCreateDirectoryFiltering run %d, test %" PRIuS, 260 loop_count, i); 261 base::File::Error expectation = 262 kFilteringTestCases[i].visible ? 263 base::File::FILE_OK : 264 base::File::FILE_ERROR_SECURITY; 265 operation_runner()->CreateDirectory( 266 url, false, false, 267 base::Bind(&ExpectEqHelper, test_name, expectation)); 268 } 269 base::MessageLoop::current()->RunUntilIdle(); 270 } 271 } 272 } 273 274 TEST_F(NativeMediaFileUtilTest, CopySourceFiltering) { 275 base::FilePath dest_path = root_path().AppendASCII("dest"); 276 FileSystemURL dest_url = CreateURL(FPL("dest")); 277 278 // Run the loop twice. The first run has no source files. The second run does. 279 for (int loop_count = 0; loop_count < 2; ++loop_count) { 280 if (loop_count == 1) { 281 PopulateDirectoryWithTestCases(root_path(), 282 kFilteringTestCases, 283 arraysize(kFilteringTestCases)); 284 } 285 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 286 // Always start with an empty destination directory. 287 // Copying to a non-empty destination directory is an invalid operation. 288 ASSERT_TRUE(base::DeleteFile(dest_path, true)); 289 ASSERT_TRUE(base::CreateDirectory(dest_path)); 290 291 FileSystemURL root_url = CreateURL(FPL("")); 292 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 293 294 std::string test_name = base::StringPrintf( 295 "CopySourceFiltering run %d test %" PRIuS, loop_count, i); 296 base::File::Error expectation = base::File::FILE_OK; 297 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 298 // If the source does not exist or is not visible. 299 expectation = base::File::FILE_ERROR_NOT_FOUND; 300 } else if (!kFilteringTestCases[i].is_directory) { 301 // Cannot copy a visible file to a directory. 302 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 303 } 304 operation_runner()->Copy( 305 url, 306 dest_url, 307 storage::FileSystemOperation::OPTION_NONE, 308 storage::FileSystemOperationRunner::CopyProgressCallback(), 309 base::Bind(&ExpectEqHelper, test_name, expectation)); 310 base::MessageLoop::current()->RunUntilIdle(); 311 } 312 } 313 } 314 315 TEST_F(NativeMediaFileUtilTest, CopyDestFiltering) { 316 // Run the loop twice. The first run has no destination files. 317 // The second run does. 318 for (int loop_count = 0; loop_count < 2; ++loop_count) { 319 if (loop_count == 1) { 320 // Reset the test directory between the two loops to remove old 321 // directories and create new ones that should pre-exist. 322 ASSERT_TRUE(base::DeleteFile(root_path(), true)); 323 ASSERT_TRUE(base::CreateDirectory(root_path())); 324 PopulateDirectoryWithTestCases(root_path(), 325 kFilteringTestCases, 326 arraysize(kFilteringTestCases)); 327 } 328 329 // Always create a dummy source data file. 330 base::FilePath src_path = root_path().AppendASCII("foo.jpg"); 331 FileSystemURL src_url = CreateURL(FPL("foo.jpg")); 332 static const char kDummyData[] = "dummy"; 333 ASSERT_TRUE(base::WriteFile(src_path, kDummyData, strlen(kDummyData))); 334 335 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 336 if (loop_count == 0 && kFilteringTestCases[i].is_directory) { 337 // These directories do not exist in this case, so Copy() will not 338 // treat them as directories. Thus invalidating these test cases. 339 continue; 340 } 341 FileSystemURL root_url = CreateURL(FPL("")); 342 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 343 344 std::string test_name = base::StringPrintf( 345 "CopyDestFiltering run %d test %" PRIuS, loop_count, i); 346 base::File::Error expectation; 347 if (loop_count == 0) { 348 // The destination path is a file here. The directory case has been 349 // handled above. 350 // If the destination path does not exist and is not visible, then 351 // creating it would be a security violation. 352 expectation = 353 kFilteringTestCases[i].visible ? 354 base::File::FILE_OK : 355 base::File::FILE_ERROR_SECURITY; 356 } else { 357 if (!kFilteringTestCases[i].visible) { 358 // If the destination path exist and is not visible, then to the copy 359 // operation, it looks like the file needs to be created, which is a 360 // security violation. 361 expectation = base::File::FILE_ERROR_SECURITY; 362 } else if (kFilteringTestCases[i].is_directory) { 363 // Cannot copy a file to a directory. 364 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 365 } else { 366 // Copying from a file to a visible file that exists is ok. 367 expectation = base::File::FILE_OK; 368 } 369 } 370 operation_runner()->Copy( 371 src_url, 372 url, 373 storage::FileSystemOperation::OPTION_NONE, 374 storage::FileSystemOperationRunner::CopyProgressCallback(), 375 base::Bind(&ExpectEqHelper, test_name, expectation)); 376 base::MessageLoop::current()->RunUntilIdle(); 377 } 378 } 379 } 380 381 TEST_F(NativeMediaFileUtilTest, MoveSourceFiltering) { 382 base::FilePath dest_path = root_path().AppendASCII("dest"); 383 FileSystemURL dest_url = CreateURL(FPL("dest")); 384 385 // Run the loop twice. The first run has no source files. The second run does. 386 for (int loop_count = 0; loop_count < 2; ++loop_count) { 387 if (loop_count == 1) { 388 PopulateDirectoryWithTestCases(root_path(), 389 kFilteringTestCases, 390 arraysize(kFilteringTestCases)); 391 } 392 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 393 // Always start with an empty destination directory. 394 // Moving to a non-empty destination directory is an invalid operation. 395 ASSERT_TRUE(base::DeleteFile(dest_path, true)); 396 ASSERT_TRUE(base::CreateDirectory(dest_path)); 397 398 FileSystemURL root_url = CreateURL(FPL("")); 399 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 400 401 std::string test_name = base::StringPrintf( 402 "MoveSourceFiltering run %d test %" PRIuS, loop_count, i); 403 base::File::Error expectation = base::File::FILE_OK; 404 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 405 // If the source does not exist or is not visible. 406 expectation = base::File::FILE_ERROR_NOT_FOUND; 407 } else if (!kFilteringTestCases[i].is_directory) { 408 // Cannot move a visible file to a directory. 409 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 410 } 411 operation_runner()->Move( 412 url, 413 dest_url, 414 storage::FileSystemOperation::OPTION_NONE, 415 base::Bind(&ExpectEqHelper, test_name, expectation)); 416 base::MessageLoop::current()->RunUntilIdle(); 417 } 418 } 419 } 420 421 TEST_F(NativeMediaFileUtilTest, MoveDestFiltering) { 422 // Run the loop twice. The first run has no destination files. 423 // The second run does. 424 for (int loop_count = 0; loop_count < 2; ++loop_count) { 425 if (loop_count == 1) { 426 // Reset the test directory between the two loops to remove old 427 // directories and create new ones that should pre-exist. 428 ASSERT_TRUE(base::DeleteFile(root_path(), true)); 429 ASSERT_TRUE(base::CreateDirectory(root_path())); 430 PopulateDirectoryWithTestCases(root_path(), 431 kFilteringTestCases, 432 arraysize(kFilteringTestCases)); 433 } 434 435 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 436 if (loop_count == 0 && kFilteringTestCases[i].is_directory) { 437 // These directories do not exist in this case, so Copy() will not 438 // treat them as directories. Thus invalidating these test cases. 439 continue; 440 } 441 442 // Create the source file for every test case because it might get moved. 443 base::FilePath src_path = root_path().AppendASCII("foo.jpg"); 444 FileSystemURL src_url = CreateURL(FPL("foo.jpg")); 445 static const char kDummyData[] = "dummy"; 446 ASSERT_TRUE( 447 base::WriteFile(src_path, kDummyData, strlen(kDummyData))); 448 449 FileSystemURL root_url = CreateURL(FPL("")); 450 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 451 452 std::string test_name = base::StringPrintf( 453 "MoveDestFiltering run %d test %" PRIuS, loop_count, i); 454 base::File::Error expectation; 455 if (loop_count == 0) { 456 // The destination path is a file here. The directory case has been 457 // handled above. 458 // If the destination path does not exist and is not visible, then 459 // creating it would be a security violation. 460 expectation = 461 kFilteringTestCases[i].visible ? 462 base::File::FILE_OK : 463 base::File::FILE_ERROR_SECURITY; 464 } else { 465 if (!kFilteringTestCases[i].visible) { 466 // If the destination path exist and is not visible, then to the move 467 // operation, it looks like the file needs to be created, which is a 468 // security violation. 469 expectation = base::File::FILE_ERROR_SECURITY; 470 } else if (kFilteringTestCases[i].is_directory) { 471 // Cannot move a file to a directory. 472 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 473 } else { 474 // Moving from a file to a visible file that exists is ok. 475 expectation = base::File::FILE_OK; 476 } 477 } 478 operation_runner()->Move( 479 src_url, 480 url, 481 storage::FileSystemOperation::OPTION_NONE, 482 base::Bind(&ExpectEqHelper, test_name, expectation)); 483 base::MessageLoop::current()->RunUntilIdle(); 484 } 485 } 486 } 487 488 TEST_F(NativeMediaFileUtilTest, GetMetadataFiltering) { 489 // Run the loop twice. The first run has no files. The second run does. 490 for (int loop_count = 0; loop_count < 2; ++loop_count) { 491 if (loop_count == 1) { 492 PopulateDirectoryWithTestCases(root_path(), 493 kFilteringTestCases, 494 arraysize(kFilteringTestCases)); 495 } 496 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 497 FileSystemURL root_url = CreateURL(FPL("")); 498 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 499 500 std::string test_name = base::StringPrintf( 501 "GetMetadataFiltering run %d test %" PRIuS, loop_count, i); 502 base::File::Error expectation = base::File::FILE_OK; 503 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 504 // Cannot get metadata from files that do not exist or are not visible. 505 expectation = base::File::FILE_ERROR_NOT_FOUND; 506 } 507 operation_runner()->GetMetadata( 508 url, 509 base::Bind(&ExpectMetadataEqHelper, 510 test_name, 511 expectation, 512 kFilteringTestCases[i].is_directory)); 513 base::MessageLoop::current()->RunUntilIdle(); 514 } 515 } 516 } 517 518 TEST_F(NativeMediaFileUtilTest, RemoveFileFiltering) { 519 // Run the loop twice. The first run has no files. The second run does. 520 for (int loop_count = 0; loop_count < 2; ++loop_count) { 521 if (loop_count == 1) { 522 PopulateDirectoryWithTestCases(root_path(), 523 kFilteringTestCases, 524 arraysize(kFilteringTestCases)); 525 } 526 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 527 FileSystemURL root_url = CreateURL(FPL("")); 528 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 529 530 std::string test_name = base::StringPrintf( 531 "RemoveFiltering run %d test %" PRIuS, loop_count, i); 532 base::File::Error expectation = base::File::FILE_OK; 533 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 534 // Cannot remove files that do not exist or are not visible. 535 expectation = base::File::FILE_ERROR_NOT_FOUND; 536 } else if (kFilteringTestCases[i].is_directory) { 537 expectation = base::File::FILE_ERROR_NOT_A_FILE; 538 } 539 operation_runner()->RemoveFile( 540 url, base::Bind(&ExpectEqHelper, test_name, expectation)); 541 base::MessageLoop::current()->RunUntilIdle(); 542 } 543 } 544 } 545 546 void CreateSnapshotCallback( 547 base::File::Error* error, 548 base::File::Error result, 549 const base::File::Info&, 550 const base::FilePath&, 551 const scoped_refptr<storage::ShareableFileReference>&) { 552 *error = result; 553 } 554 555 TEST_F(NativeMediaFileUtilTest, CreateSnapshot) { 556 PopulateDirectoryWithTestCases(root_path(), 557 kFilteringTestCases, 558 arraysize(kFilteringTestCases)); 559 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 560 if (kFilteringTestCases[i].is_directory || 561 !kFilteringTestCases[i].visible) { 562 continue; 563 } 564 FileSystemURL root_url = CreateURL(FPL("")); 565 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 566 base::File::Error expected_error, error; 567 if (kFilteringTestCases[i].media_file) 568 expected_error = base::File::FILE_OK; 569 else 570 expected_error = base::File::FILE_ERROR_SECURITY; 571 error = base::File::FILE_ERROR_FAILED; 572 operation_runner()->CreateSnapshotFile(url, 573 base::Bind(CreateSnapshotCallback, &error)); 574 base::MessageLoop::current()->RunUntilIdle(); 575 ASSERT_EQ(expected_error, error); 576 } 577 } 578