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