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 "content/public/test/test_file_system_options.h"
20#include "testing/gtest/include/gtest/gtest.h"
21#include "webkit/browser/fileapi/external_mount_points.h"
22#include "webkit/browser/fileapi/file_system_backend.h"
23#include "webkit/browser/fileapi/file_system_context.h"
24#include "webkit/browser/fileapi/file_system_operation_runner.h"
25#include "webkit/browser/fileapi/file_system_url.h"
26#include "webkit/browser/fileapi/isolated_context.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
32using fileapi::FileSystemOperation;
33using fileapi::FileSystemURL;
34
35namespace {
36
37typedef FileSystemOperation::FileEntryList FileEntryList;
38
39struct FilteringTestCase {
40  const base::FilePath::CharType* path;
41  bool is_directory;
42  bool visible;
43  bool media_file;
44  const char* content;
45};
46
47const 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
67void ExpectEqHelper(const std::string& test_name,
68                    base::PlatformFileError expected,
69                    base::PlatformFileError actual) {
70  EXPECT_EQ(expected, actual) << test_name;
71}
72
73void ExpectMetadataEqHelper(const std::string& test_name,
74                            base::PlatformFileError expected,
75                            bool expected_is_directory,
76                            base::PlatformFileError actual,
77                            const base::PlatformFileInfo& file_info) {
78  EXPECT_EQ(expected, actual) << test_name;
79  if (actual == base::PLATFORM_FILE_OK)
80    EXPECT_EQ(expected_is_directory, file_info.is_directory) << test_name;
81}
82
83void DidReadDirectory(std::set<base::FilePath::StringType>* content,
84                      bool* completed,
85                      base::PlatformFileError 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
95void 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, file_util::WriteFile(path, test_cases[i].content, len));
106    }
107  }
108}
109
110}  // namespace
111
112class 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 quota::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        data_dir_.path(),
137        fileapi::CreateAllowFileAccessOptions());
138
139    filesystem_id_ = isolated_context()->RegisterFileSystemForPath(
140        fileapi::kFileSystemTypeNativeMedia, root_path(), NULL);
141
142    isolated_context()->AddReference(filesystem_id_);
143  }
144
145  virtual void TearDown() {
146    isolated_context()->RemoveReference(filesystem_id_);
147    file_system_context_ = NULL;
148  }
149
150 protected:
151  fileapi::FileSystemContext* file_system_context() {
152    return file_system_context_.get();
153  }
154
155  FileSystemURL CreateURL(const base::FilePath::CharType* test_case_path) {
156    return file_system_context_->CreateCrackedFileSystemURL(
157        origin(),
158        fileapi::kFileSystemTypeIsolated,
159        GetVirtualPath(test_case_path));
160  }
161
162  fileapi::IsolatedContext* isolated_context() {
163    return fileapi::IsolatedContext::GetInstance();
164  }
165
166  base::FilePath root_path() {
167    return data_dir_.path().Append(FPL("Media Directory"));
168  }
169
170  base::FilePath GetVirtualPath(
171      const base::FilePath::CharType* test_case_path) {
172    return base::FilePath::FromUTF8Unsafe(filesystem_id_).
173               Append(FPL("Media Directory")).
174               Append(base::FilePath(test_case_path));
175  }
176
177  GURL origin() {
178    return GURL("http://example.com");
179  }
180
181  fileapi::FileSystemType type() {
182    return fileapi::kFileSystemTypeNativeMedia;
183  }
184
185  fileapi::FileSystemOperationRunner* operation_runner() {
186    return file_system_context_->operation_runner();
187  }
188
189 private:
190  base::MessageLoop message_loop_;
191  content::TestBrowserThread io_thread_;
192
193  base::ScopedTempDir data_dir_;
194  scoped_refptr<fileapi::FileSystemContext> file_system_context_;
195
196  std::string filesystem_id_;
197
198  DISALLOW_COPY_AND_ASSIGN(NativeMediaFileUtilTest);
199};
200
201TEST_F(NativeMediaFileUtilTest, DirectoryExistsAndFileExistsFiltering) {
202  PopulateDirectoryWithTestCases(root_path(),
203                                 kFilteringTestCases,
204                                 arraysize(kFilteringTestCases));
205
206  for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
207    FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
208
209    base::PlatformFileError expectation =
210        kFilteringTestCases[i].visible ?
211        base::PLATFORM_FILE_OK :
212        base::PLATFORM_FILE_ERROR_NOT_FOUND;
213
214    std::string test_name =
215        base::StringPrintf("DirectoryExistsAndFileExistsFiltering %" PRIuS, i);
216    if (kFilteringTestCases[i].is_directory) {
217      operation_runner()->DirectoryExists(
218          url, base::Bind(&ExpectEqHelper, test_name, expectation));
219    } else {
220      operation_runner()->FileExists(
221          url, base::Bind(&ExpectEqHelper, test_name, expectation));
222    }
223    base::MessageLoop::current()->RunUntilIdle();
224  }
225}
226
227TEST_F(NativeMediaFileUtilTest, ReadDirectoryFiltering) {
228  PopulateDirectoryWithTestCases(root_path(),
229                                 kFilteringTestCases,
230                                 arraysize(kFilteringTestCases));
231
232  std::set<base::FilePath::StringType> content;
233  FileSystemURL url = CreateURL(FPL(""));
234  bool completed = false;
235  operation_runner()->ReadDirectory(
236      url, base::Bind(&DidReadDirectory, &content, &completed));
237  base::MessageLoop::current()->RunUntilIdle();
238  EXPECT_TRUE(completed);
239  EXPECT_EQ(6u, content.size());
240
241  for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
242    base::FilePath::StringType name =
243        base::FilePath(kFilteringTestCases[i].path).BaseName().value();
244    std::set<base::FilePath::StringType>::const_iterator found =
245        content.find(name);
246    EXPECT_EQ(kFilteringTestCases[i].visible, found != content.end());
247  }
248}
249
250TEST_F(NativeMediaFileUtilTest, CreateDirectoryFiltering) {
251  // Run the loop twice. The second loop attempts to create directories that are
252  // pre-existing. Though the result should be the same.
253  for (int loop_count = 0; loop_count < 2; ++loop_count) {
254    for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
255      if (kFilteringTestCases[i].is_directory) {
256        FileSystemURL root_url = CreateURL(FPL(""));
257        FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
258
259        std::string test_name = base::StringPrintf(
260            "CreateFileAndCreateDirectoryFiltering run %d, test %" PRIuS,
261            loop_count, i);
262        base::PlatformFileError expectation =
263            kFilteringTestCases[i].visible ?
264            base::PLATFORM_FILE_OK :
265            base::PLATFORM_FILE_ERROR_SECURITY;
266        operation_runner()->CreateDirectory(
267            url, false, false,
268            base::Bind(&ExpectEqHelper, test_name, expectation));
269      }
270      base::MessageLoop::current()->RunUntilIdle();
271    }
272  }
273}
274
275TEST_F(NativeMediaFileUtilTest, CopySourceFiltering) {
276  base::FilePath dest_path = root_path().AppendASCII("dest");
277  FileSystemURL dest_url = CreateURL(FPL("dest"));
278
279  // Run the loop twice. The first run has no source files. The second run does.
280  for (int loop_count = 0; loop_count < 2; ++loop_count) {
281    if (loop_count == 1) {
282      PopulateDirectoryWithTestCases(root_path(),
283                                     kFilteringTestCases,
284                                     arraysize(kFilteringTestCases));
285    }
286    for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
287      // Always start with an empty destination directory.
288      // Copying to a non-empty destination directory is an invalid operation.
289      ASSERT_TRUE(base::DeleteFile(dest_path, true));
290      ASSERT_TRUE(base::CreateDirectory(dest_path));
291
292      FileSystemURL root_url = CreateURL(FPL(""));
293      FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
294
295      std::string test_name = base::StringPrintf(
296          "CopySourceFiltering run %d test %" PRIuS, loop_count, i);
297      base::PlatformFileError expectation = base::PLATFORM_FILE_OK;
298      if (loop_count == 0 || !kFilteringTestCases[i].visible) {
299        // If the source does not exist or is not visible.
300        expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND;
301      } else if (!kFilteringTestCases[i].is_directory) {
302        // Cannot copy a visible file to a directory.
303        expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
304      }
305      operation_runner()->Copy(
306          url, dest_url,
307          fileapi::FileSystemOperation::OPTION_NONE,
308          fileapi::FileSystemOperationRunner::CopyProgressCallback(),
309          base::Bind(&ExpectEqHelper, test_name, expectation));
310      base::MessageLoop::current()->RunUntilIdle();
311    }
312  }
313}
314
315TEST_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(file_util::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::PlatformFileError 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::PLATFORM_FILE_OK :
355            base::PLATFORM_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::PLATFORM_FILE_ERROR_SECURITY;
362        } else if (kFilteringTestCases[i].is_directory) {
363          // Cannot copy a file to a directory.
364          expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
365        } else {
366          // Copying from a file to a visible file that exists is ok.
367          expectation = base::PLATFORM_FILE_OK;
368        }
369      }
370      operation_runner()->Copy(
371          src_url, url,
372          fileapi::FileSystemOperation::OPTION_NONE,
373          fileapi::FileSystemOperationRunner::CopyProgressCallback(),
374          base::Bind(&ExpectEqHelper, test_name, expectation));
375      base::MessageLoop::current()->RunUntilIdle();
376    }
377  }
378}
379
380TEST_F(NativeMediaFileUtilTest, MoveSourceFiltering) {
381  base::FilePath dest_path = root_path().AppendASCII("dest");
382  FileSystemURL dest_url = CreateURL(FPL("dest"));
383
384  // Run the loop twice. The first run has no source files. The second run does.
385  for (int loop_count = 0; loop_count < 2; ++loop_count) {
386    if (loop_count == 1) {
387      PopulateDirectoryWithTestCases(root_path(),
388                                     kFilteringTestCases,
389                                     arraysize(kFilteringTestCases));
390    }
391    for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
392      // Always start with an empty destination directory.
393      // Moving to a non-empty destination directory is an invalid operation.
394      ASSERT_TRUE(base::DeleteFile(dest_path, true));
395      ASSERT_TRUE(base::CreateDirectory(dest_path));
396
397      FileSystemURL root_url = CreateURL(FPL(""));
398      FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
399
400      std::string test_name = base::StringPrintf(
401          "MoveSourceFiltering run %d test %" PRIuS, loop_count, i);
402      base::PlatformFileError expectation = base::PLATFORM_FILE_OK;
403      if (loop_count == 0 || !kFilteringTestCases[i].visible) {
404        // If the source does not exist or is not visible.
405        expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND;
406      } else if (!kFilteringTestCases[i].is_directory) {
407        // Cannot move a visible file to a directory.
408        expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
409      }
410      operation_runner()->Move(
411          url, dest_url, fileapi::FileSystemOperation::OPTION_NONE,
412          base::Bind(&ExpectEqHelper, test_name, expectation));
413      base::MessageLoop::current()->RunUntilIdle();
414    }
415  }
416}
417
418TEST_F(NativeMediaFileUtilTest, MoveDestFiltering) {
419  // Run the loop twice. The first run has no destination files.
420  // The second run does.
421  for (int loop_count = 0; loop_count < 2; ++loop_count) {
422    if (loop_count == 1) {
423      // Reset the test directory between the two loops to remove old
424      // directories and create new ones that should pre-exist.
425      ASSERT_TRUE(base::DeleteFile(root_path(), true));
426      ASSERT_TRUE(base::CreateDirectory(root_path()));
427      PopulateDirectoryWithTestCases(root_path(),
428                                     kFilteringTestCases,
429                                     arraysize(kFilteringTestCases));
430    }
431
432    for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
433      if (loop_count == 0 && kFilteringTestCases[i].is_directory) {
434        // These directories do not exist in this case, so Copy() will not
435        // treat them as directories. Thus invalidating these test cases.
436        continue;
437      }
438
439      // Create the source file for every test case because it might get moved.
440      base::FilePath src_path = root_path().AppendASCII("foo.jpg");
441      FileSystemURL src_url = CreateURL(FPL("foo.jpg"));
442      static const char kDummyData[] = "dummy";
443      ASSERT_TRUE(
444          file_util::WriteFile(src_path, kDummyData, strlen(kDummyData)));
445
446      FileSystemURL root_url = CreateURL(FPL(""));
447      FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
448
449      std::string test_name = base::StringPrintf(
450          "MoveDestFiltering run %d test %" PRIuS, loop_count, i);
451      base::PlatformFileError expectation;
452      if (loop_count == 0) {
453        // The destination path is a file here. The directory case has been
454        // handled above.
455        // If the destination path does not exist and is not visible, then
456        // creating it would be a security violation.
457        expectation =
458            kFilteringTestCases[i].visible ?
459            base::PLATFORM_FILE_OK :
460            base::PLATFORM_FILE_ERROR_SECURITY;
461      } else {
462        if (!kFilteringTestCases[i].visible) {
463          // If the destination path exist and is not visible, then to the move
464          // operation, it looks like the file needs to be created, which is a
465          // security violation.
466          expectation = base::PLATFORM_FILE_ERROR_SECURITY;
467        } else if (kFilteringTestCases[i].is_directory) {
468          // Cannot move a file to a directory.
469          expectation = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
470        } else {
471          // Moving from a file to a visible file that exists is ok.
472          expectation = base::PLATFORM_FILE_OK;
473        }
474      }
475      operation_runner()->Move(
476          src_url, url, fileapi::FileSystemOperation::OPTION_NONE,
477          base::Bind(&ExpectEqHelper, test_name, expectation));
478      base::MessageLoop::current()->RunUntilIdle();
479    }
480  }
481}
482
483TEST_F(NativeMediaFileUtilTest, GetMetadataFiltering) {
484  // Run the loop twice. The first run has no files. The second run does.
485  for (int loop_count = 0; loop_count < 2; ++loop_count) {
486    if (loop_count == 1) {
487      PopulateDirectoryWithTestCases(root_path(),
488                                     kFilteringTestCases,
489                                     arraysize(kFilteringTestCases));
490    }
491    for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
492      FileSystemURL root_url = CreateURL(FPL(""));
493      FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
494
495      std::string test_name = base::StringPrintf(
496          "GetMetadataFiltering run %d test %" PRIuS, loop_count, i);
497      base::PlatformFileError expectation = base::PLATFORM_FILE_OK;
498      if (loop_count == 0 || !kFilteringTestCases[i].visible) {
499        // Cannot get metadata from files that do not exist or are not visible.
500        expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND;
501      }
502      operation_runner()->GetMetadata(
503          url,
504          base::Bind(&ExpectMetadataEqHelper,
505                     test_name,
506                     expectation,
507                     kFilteringTestCases[i].is_directory));
508      base::MessageLoop::current()->RunUntilIdle();
509    }
510  }
511}
512
513TEST_F(NativeMediaFileUtilTest, RemoveFileFiltering) {
514  // Run the loop twice. The first run has no files. The second run does.
515  for (int loop_count = 0; loop_count < 2; ++loop_count) {
516    if (loop_count == 1) {
517      PopulateDirectoryWithTestCases(root_path(),
518                                     kFilteringTestCases,
519                                     arraysize(kFilteringTestCases));
520    }
521    for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
522      FileSystemURL root_url = CreateURL(FPL(""));
523      FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
524
525      std::string test_name = base::StringPrintf(
526          "RemoveFiltering run %d test %" PRIuS, loop_count, i);
527      base::PlatformFileError expectation = base::PLATFORM_FILE_OK;
528      if (loop_count == 0 || !kFilteringTestCases[i].visible) {
529        // Cannot remove files that do not exist or are not visible.
530        expectation = base::PLATFORM_FILE_ERROR_NOT_FOUND;
531      } else if (kFilteringTestCases[i].is_directory) {
532        expectation = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
533      }
534      operation_runner()->RemoveFile(
535          url, base::Bind(&ExpectEqHelper, test_name, expectation));
536      base::MessageLoop::current()->RunUntilIdle();
537    }
538  }
539}
540
541void CreateSnapshotCallback(base::PlatformFileError* error,
542    base::PlatformFileError result, const base::PlatformFileInfo&,
543    const base::FilePath&,
544    const scoped_refptr<webkit_blob::ShareableFileReference>&) {
545  *error = result;
546}
547
548TEST_F(NativeMediaFileUtilTest, CreateSnapshot) {
549  PopulateDirectoryWithTestCases(root_path(),
550                                 kFilteringTestCases,
551                                 arraysize(kFilteringTestCases));
552  for (size_t i = 0; i < arraysize(kFilteringTestCases); ++i) {
553    if (kFilteringTestCases[i].is_directory ||
554        !kFilteringTestCases[i].visible) {
555      continue;
556    }
557    FileSystemURL root_url = CreateURL(FPL(""));
558    FileSystemURL url = CreateURL(kFilteringTestCases[i].path);
559    base::PlatformFileError expected_error, error;
560    if (kFilteringTestCases[i].media_file)
561      expected_error = base::PLATFORM_FILE_OK;
562    else
563      expected_error = base::PLATFORM_FILE_ERROR_SECURITY;
564    error = base::PLATFORM_FILE_ERROR_FAILED;
565    operation_runner()->CreateSnapshotFile(url,
566        base::Bind(CreateSnapshotCallback, &error));
567    base::MessageLoop::current()->RunUntilIdle();
568    ASSERT_EQ(expected_error, error);
569  }
570}
571