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/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 "testing/gtest/include/gtest/gtest.h" 22 #include "webkit/browser/fileapi/external_mount_points.h" 23 #include "webkit/browser/fileapi/file_system_backend.h" 24 #include "webkit/browser/fileapi/file_system_context.h" 25 #include "webkit/browser/fileapi/file_system_operation_runner.h" 26 #include "webkit/browser/fileapi/file_system_url.h" 27 #include "webkit/browser/fileapi/isolated_context.h" 28 #include "webkit/browser/fileapi/native_file_util.h" 29 30 #define FPL(x) FILE_PATH_LITERAL(x) 31 32 using fileapi::FileSystemOperation; 33 using fileapi::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<quota::SpecialStoragePolicy> storage_policy = 123 new content::MockSpecialStoragePolicy(); 124 125 ScopedVector<fileapi::FileSystemBackend> additional_providers; 126 additional_providers.push_back(new MediaFileSystemBackend( 127 data_dir_.path(), base::MessageLoopProxy::current().get())); 128 129 file_system_context_ = new fileapi::FileSystemContext( 130 base::MessageLoopProxy::current().get(), 131 base::MessageLoopProxy::current().get(), 132 fileapi::ExternalMountPoints::CreateRefCounted().get(), 133 storage_policy.get(), 134 NULL, 135 additional_providers.Pass(), 136 std::vector<fileapi::URLRequestAutoMountHandler>(), 137 data_dir_.path(), 138 content::CreateAllowFileAccessOptions()); 139 140 filesystem_id_ = isolated_context()->RegisterFileSystemForPath( 141 fileapi::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 fileapi::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 fileapi::kFileSystemTypeIsolated, 160 GetVirtualPath(test_case_path)); 161 } 162 163 fileapi::IsolatedContext* isolated_context() { 164 return fileapi::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 fileapi::FileSystemType type() { 183 return fileapi::kFileSystemTypeNativeMedia; 184 } 185 186 fileapi::FileSystemOperationRunner* operation_runner() { 187 return file_system_context_->operation_runner(); 188 } 189 190 private: 191 base::MessageLoop message_loop_; 192 content::TestBrowserThread io_thread_; 193 194 base::ScopedTempDir data_dir_; 195 scoped_refptr<fileapi::FileSystemContext> file_system_context_; 196 197 std::string filesystem_id_; 198 199 DISALLOW_COPY_AND_ASSIGN(NativeMediaFileUtilTest); 200 }; 201 202 TEST_F(NativeMediaFileUtilTest, DirectoryExistsAndFileExistsFiltering) { 203 PopulateDirectoryWithTestCases(root_path(), 204 kFilteringTestCases, 205 arraysize(kFilteringTestCases)); 206 207 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 208 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 209 210 base::File::Error expectation = 211 kFilteringTestCases[i].visible ? 212 base::File::FILE_OK : 213 base::File::FILE_ERROR_NOT_FOUND; 214 215 std::string test_name = 216 base::StringPrintf("DirectoryExistsAndFileExistsFiltering %" PRIuS, i); 217 if (kFilteringTestCases[i].is_directory) { 218 operation_runner()->DirectoryExists( 219 url, base::Bind(&ExpectEqHelper, test_name, expectation)); 220 } else { 221 operation_runner()->FileExists( 222 url, base::Bind(&ExpectEqHelper, test_name, expectation)); 223 } 224 base::MessageLoop::current()->RunUntilIdle(); 225 } 226 } 227 228 TEST_F(NativeMediaFileUtilTest, ReadDirectoryFiltering) { 229 PopulateDirectoryWithTestCases(root_path(), 230 kFilteringTestCases, 231 arraysize(kFilteringTestCases)); 232 233 std::set<base::FilePath::StringType> content; 234 FileSystemURL url = CreateURL(FPL("")); 235 bool completed = false; 236 operation_runner()->ReadDirectory( 237 url, base::Bind(&DidReadDirectory, &content, &completed)); 238 base::MessageLoop::current()->RunUntilIdle(); 239 EXPECT_TRUE(completed); 240 EXPECT_EQ(6u, content.size()); 241 242 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 243 base::FilePath::StringType name = 244 base::FilePath(kFilteringTestCases[i].path).BaseName().value(); 245 std::set<base::FilePath::StringType>::const_iterator found = 246 content.find(name); 247 EXPECT_EQ(kFilteringTestCases[i].visible, found != content.end()); 248 } 249 } 250 251 TEST_F(NativeMediaFileUtilTest, CreateDirectoryFiltering) { 252 // Run the loop twice. The second loop attempts to create directories that are 253 // pre-existing. Though the result should be the same. 254 for (int loop_count = 0; loop_count < 2; ++loop_count) { 255 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 256 if (kFilteringTestCases[i].is_directory) { 257 FileSystemURL root_url = CreateURL(FPL("")); 258 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 259 260 std::string test_name = base::StringPrintf( 261 "CreateFileAndCreateDirectoryFiltering run %d, test %" PRIuS, 262 loop_count, i); 263 base::File::Error expectation = 264 kFilteringTestCases[i].visible ? 265 base::File::FILE_OK : 266 base::File::FILE_ERROR_SECURITY; 267 operation_runner()->CreateDirectory( 268 url, false, false, 269 base::Bind(&ExpectEqHelper, test_name, expectation)); 270 } 271 base::MessageLoop::current()->RunUntilIdle(); 272 } 273 } 274 } 275 276 TEST_F(NativeMediaFileUtilTest, CopySourceFiltering) { 277 base::FilePath dest_path = root_path().AppendASCII("dest"); 278 FileSystemURL dest_url = CreateURL(FPL("dest")); 279 280 // Run the loop twice. The first run has no source files. The second run does. 281 for (int loop_count = 0; loop_count < 2; ++loop_count) { 282 if (loop_count == 1) { 283 PopulateDirectoryWithTestCases(root_path(), 284 kFilteringTestCases, 285 arraysize(kFilteringTestCases)); 286 } 287 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 288 // Always start with an empty destination directory. 289 // Copying to a non-empty destination directory is an invalid operation. 290 ASSERT_TRUE(base::DeleteFile(dest_path, true)); 291 ASSERT_TRUE(base::CreateDirectory(dest_path)); 292 293 FileSystemURL root_url = CreateURL(FPL("")); 294 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 295 296 std::string test_name = base::StringPrintf( 297 "CopySourceFiltering run %d test %" PRIuS, loop_count, i); 298 base::File::Error expectation = base::File::FILE_OK; 299 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 300 // If the source does not exist or is not visible. 301 expectation = base::File::FILE_ERROR_NOT_FOUND; 302 } else if (!kFilteringTestCases[i].is_directory) { 303 // Cannot copy a visible file to a directory. 304 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 305 } 306 operation_runner()->Copy( 307 url, dest_url, 308 fileapi::FileSystemOperation::OPTION_NONE, 309 fileapi::FileSystemOperationRunner::CopyProgressCallback(), 310 base::Bind(&ExpectEqHelper, test_name, expectation)); 311 base::MessageLoop::current()->RunUntilIdle(); 312 } 313 } 314 } 315 316 TEST_F(NativeMediaFileUtilTest, CopyDestFiltering) { 317 // Run the loop twice. The first run has no destination files. 318 // The second run does. 319 for (int loop_count = 0; loop_count < 2; ++loop_count) { 320 if (loop_count == 1) { 321 // Reset the test directory between the two loops to remove old 322 // directories and create new ones that should pre-exist. 323 ASSERT_TRUE(base::DeleteFile(root_path(), true)); 324 ASSERT_TRUE(base::CreateDirectory(root_path())); 325 PopulateDirectoryWithTestCases(root_path(), 326 kFilteringTestCases, 327 arraysize(kFilteringTestCases)); 328 } 329 330 // Always create a dummy source data file. 331 base::FilePath src_path = root_path().AppendASCII("foo.jpg"); 332 FileSystemURL src_url = CreateURL(FPL("foo.jpg")); 333 static const char kDummyData[] = "dummy"; 334 ASSERT_TRUE(base::WriteFile(src_path, kDummyData, strlen(kDummyData))); 335 336 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 337 if (loop_count == 0 && kFilteringTestCases[i].is_directory) { 338 // These directories do not exist in this case, so Copy() will not 339 // treat them as directories. Thus invalidating these test cases. 340 continue; 341 } 342 FileSystemURL root_url = CreateURL(FPL("")); 343 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 344 345 std::string test_name = base::StringPrintf( 346 "CopyDestFiltering run %d test %" PRIuS, loop_count, i); 347 base::File::Error expectation; 348 if (loop_count == 0) { 349 // The destination path is a file here. The directory case has been 350 // handled above. 351 // If the destination path does not exist and is not visible, then 352 // creating it would be a security violation. 353 expectation = 354 kFilteringTestCases[i].visible ? 355 base::File::FILE_OK : 356 base::File::FILE_ERROR_SECURITY; 357 } else { 358 if (!kFilteringTestCases[i].visible) { 359 // If the destination path exist and is not visible, then to the copy 360 // operation, it looks like the file needs to be created, which is a 361 // security violation. 362 expectation = base::File::FILE_ERROR_SECURITY; 363 } else if (kFilteringTestCases[i].is_directory) { 364 // Cannot copy a file to a directory. 365 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 366 } else { 367 // Copying from a file to a visible file that exists is ok. 368 expectation = base::File::FILE_OK; 369 } 370 } 371 operation_runner()->Copy( 372 src_url, url, 373 fileapi::FileSystemOperation::OPTION_NONE, 374 fileapi::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, dest_url, fileapi::FileSystemOperation::OPTION_NONE, 413 base::Bind(&ExpectEqHelper, test_name, expectation)); 414 base::MessageLoop::current()->RunUntilIdle(); 415 } 416 } 417 } 418 419 TEST_F(NativeMediaFileUtilTest, MoveDestFiltering) { 420 // Run the loop twice. The first run has no destination files. 421 // The second run does. 422 for (int loop_count = 0; loop_count < 2; ++loop_count) { 423 if (loop_count == 1) { 424 // Reset the test directory between the two loops to remove old 425 // directories and create new ones that should pre-exist. 426 ASSERT_TRUE(base::DeleteFile(root_path(), true)); 427 ASSERT_TRUE(base::CreateDirectory(root_path())); 428 PopulateDirectoryWithTestCases(root_path(), 429 kFilteringTestCases, 430 arraysize(kFilteringTestCases)); 431 } 432 433 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 434 if (loop_count == 0 && kFilteringTestCases[i].is_directory) { 435 // These directories do not exist in this case, so Copy() will not 436 // treat them as directories. Thus invalidating these test cases. 437 continue; 438 } 439 440 // Create the source file for every test case because it might get moved. 441 base::FilePath src_path = root_path().AppendASCII("foo.jpg"); 442 FileSystemURL src_url = CreateURL(FPL("foo.jpg")); 443 static const char kDummyData[] = "dummy"; 444 ASSERT_TRUE( 445 base::WriteFile(src_path, kDummyData, strlen(kDummyData))); 446 447 FileSystemURL root_url = CreateURL(FPL("")); 448 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 449 450 std::string test_name = base::StringPrintf( 451 "MoveDestFiltering run %d test %" PRIuS, loop_count, i); 452 base::File::Error expectation; 453 if (loop_count == 0) { 454 // The destination path is a file here. The directory case has been 455 // handled above. 456 // If the destination path does not exist and is not visible, then 457 // creating it would be a security violation. 458 expectation = 459 kFilteringTestCases[i].visible ? 460 base::File::FILE_OK : 461 base::File::FILE_ERROR_SECURITY; 462 } else { 463 if (!kFilteringTestCases[i].visible) { 464 // If the destination path exist and is not visible, then to the move 465 // operation, it looks like the file needs to be created, which is a 466 // security violation. 467 expectation = base::File::FILE_ERROR_SECURITY; 468 } else if (kFilteringTestCases[i].is_directory) { 469 // Cannot move a file to a directory. 470 expectation = base::File::FILE_ERROR_INVALID_OPERATION; 471 } else { 472 // Moving from a file to a visible file that exists is ok. 473 expectation = base::File::FILE_OK; 474 } 475 } 476 operation_runner()->Move( 477 src_url, url, fileapi::FileSystemOperation::OPTION_NONE, 478 base::Bind(&ExpectEqHelper, test_name, expectation)); 479 base::MessageLoop::current()->RunUntilIdle(); 480 } 481 } 482 } 483 484 TEST_F(NativeMediaFileUtilTest, GetMetadataFiltering) { 485 // Run the loop twice. The first run has no files. The second run does. 486 for (int loop_count = 0; loop_count < 2; ++loop_count) { 487 if (loop_count == 1) { 488 PopulateDirectoryWithTestCases(root_path(), 489 kFilteringTestCases, 490 arraysize(kFilteringTestCases)); 491 } 492 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 493 FileSystemURL root_url = CreateURL(FPL("")); 494 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 495 496 std::string test_name = base::StringPrintf( 497 "GetMetadataFiltering run %d test %" PRIuS, loop_count, i); 498 base::File::Error expectation = base::File::FILE_OK; 499 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 500 // Cannot get metadata from files that do not exist or are not visible. 501 expectation = base::File::FILE_ERROR_NOT_FOUND; 502 } 503 operation_runner()->GetMetadata( 504 url, 505 base::Bind(&ExpectMetadataEqHelper, 506 test_name, 507 expectation, 508 kFilteringTestCases[i].is_directory)); 509 base::MessageLoop::current()->RunUntilIdle(); 510 } 511 } 512 } 513 514 TEST_F(NativeMediaFileUtilTest, RemoveFileFiltering) { 515 // Run the loop twice. The first run has no files. The second run does. 516 for (int loop_count = 0; loop_count < 2; ++loop_count) { 517 if (loop_count == 1) { 518 PopulateDirectoryWithTestCases(root_path(), 519 kFilteringTestCases, 520 arraysize(kFilteringTestCases)); 521 } 522 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 523 FileSystemURL root_url = CreateURL(FPL("")); 524 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 525 526 std::string test_name = base::StringPrintf( 527 "RemoveFiltering run %d test %" PRIuS, loop_count, i); 528 base::File::Error expectation = base::File::FILE_OK; 529 if (loop_count == 0 || !kFilteringTestCases[i].visible) { 530 // Cannot remove files that do not exist or are not visible. 531 expectation = base::File::FILE_ERROR_NOT_FOUND; 532 } else if (kFilteringTestCases[i].is_directory) { 533 expectation = base::File::FILE_ERROR_NOT_A_FILE; 534 } 535 operation_runner()->RemoveFile( 536 url, base::Bind(&ExpectEqHelper, test_name, expectation)); 537 base::MessageLoop::current()->RunUntilIdle(); 538 } 539 } 540 } 541 542 void CreateSnapshotCallback( 543 base::File::Error* error, 544 base::File::Error result, 545 const base::File::Info&, 546 const base::FilePath&, 547 const scoped_refptr<webkit_blob::ShareableFileReference>&) { 548 *error = result; 549 } 550 551 TEST_F(NativeMediaFileUtilTest, CreateSnapshot) { 552 PopulateDirectoryWithTestCases(root_path(), 553 kFilteringTestCases, 554 arraysize(kFilteringTestCases)); 555 for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) { 556 if (kFilteringTestCases[i].is_directory || 557 !kFilteringTestCases[i].visible) { 558 continue; 559 } 560 FileSystemURL root_url = CreateURL(FPL("")); 561 FileSystemURL url = CreateURL(kFilteringTestCases[i].path); 562 base::File::Error expected_error, error; 563 if (kFilteringTestCases[i].media_file) 564 expected_error = base::File::FILE_OK; 565 else 566 expected_error = base::File::FILE_ERROR_SECURITY; 567 error = base::File::FILE_ERROR_FAILED; 568 operation_runner()->CreateSnapshotFile(url, 569 base::Bind(CreateSnapshotCallback, &error)); 570 base::MessageLoop::current()->RunUntilIdle(); 571 ASSERT_EQ(expected_error, error); 572 } 573 } 574