1 // Copyright 2014 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 <stddef.h>
6 
7 #include <string>
8 
9 #include "base/macros.h"
10 #include "base/stl_util.h"
11 #include "storage/browser/file_system/file_system_url.h"
12 #include "storage/browser/file_system/isolated_context.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 
15 #define FPL(x) FILE_PATH_LITERAL(x)
16 
17 #if defined(FILE_PATH_USES_DRIVE_LETTERS)
18 #define DRIVE FPL("C:")
19 #else
20 #define DRIVE
21 #endif
22 
23 namespace storage {
24 
25 using FileInfo = IsolatedContext::MountPointInfo;
26 
27 namespace {
28 
29 const base::FilePath kTestPaths[] = {
30     base::FilePath(DRIVE FPL("/a/b.txt")),
31     base::FilePath(DRIVE FPL("/c/d/e")),
32     base::FilePath(DRIVE FPL("/h/")),
33     base::FilePath(DRIVE FPL("/")),
34 #if defined(FILE_PATH_USES_WIN_SEPARATORS)
35     base::FilePath(DRIVE FPL("\\foo\\bar")),
36     base::FilePath(DRIVE FPL("\\")),
37 #endif
38     // For duplicated base name test.
39     base::FilePath(DRIVE FPL("/")),
40     base::FilePath(DRIVE FPL("/f/e")),
41     base::FilePath(DRIVE FPL("/f/b.txt")),
42 };
43 
44 }  // namespace
45 
46 class IsolatedContextTest : public testing::Test {
47  public:
IsolatedContextTest()48   IsolatedContextTest() {
49     for (const auto& path : kTestPaths)
50       fileset_.insert(path.NormalizePathSeparators());
51   }
52 
SetUp()53   void SetUp() override {
54     IsolatedContext::FileInfoSet files;
55     for (const auto& path : kTestPaths) {
56       std::string name;
57       ASSERT_TRUE(files.AddPath(path.NormalizePathSeparators(), &name));
58       names_.push_back(name);
59     }
60     id_ = IsolatedContext::GetInstance()->RegisterDraggedFileSystem(files);
61     IsolatedContext::GetInstance()->AddReference(id_);
62     ASSERT_FALSE(id_.empty());
63   }
64 
TearDown()65   void TearDown() override {
66     IsolatedContext::GetInstance()->RemoveReference(id_);
67   }
68 
isolated_context() const69   IsolatedContext* isolated_context() const {
70     return IsolatedContext::GetInstance();
71   }
72 
73  protected:
74   std::string id_;
75   std::multiset<base::FilePath> fileset_;
76   std::vector<std::string> names_;
77 
78  private:
79   DISALLOW_COPY_AND_ASSIGN(IsolatedContextTest);
80 };
81 
TEST_F(IsolatedContextTest,RegisterAndRevokeTest)82 TEST_F(IsolatedContextTest, RegisterAndRevokeTest) {
83   // See if the returned top-level entries match with what we registered.
84   std::vector<FileInfo> toplevels;
85   ASSERT_TRUE(isolated_context()->GetDraggedFileInfo(id_, &toplevels));
86   ASSERT_EQ(fileset_.size(), toplevels.size());
87   for (size_t i = 0; i < toplevels.size(); ++i) {
88     ASSERT_TRUE(fileset_.find(toplevels[i].path) != fileset_.end());
89   }
90 
91   // See if the name of each registered kTestPaths (that is what we
92   // register in SetUp() by RegisterDraggedFileSystem) is properly cracked as
93   // a valid virtual path in the isolated filesystem.
94   for (size_t i = 0; i < base::size(kTestPaths); ++i) {
95     base::FilePath virtual_path =
96         isolated_context()->CreateVirtualRootPath(id_).AppendASCII(names_[i]);
97     std::string cracked_id;
98     base::FilePath cracked_path;
99     std::string cracked_inner_id;
100     FileSystemType cracked_type;
101     FileSystemMountOption cracked_option;
102     ASSERT_TRUE(isolated_context()->CrackVirtualPath(
103         virtual_path, &cracked_id, &cracked_type, &cracked_inner_id,
104         &cracked_path, &cracked_option));
105     ASSERT_EQ(kTestPaths[i].NormalizePathSeparators().value(),
106               cracked_path.value());
107     ASSERT_EQ(id_, cracked_id);
108     ASSERT_EQ(kFileSystemTypeDragged, cracked_type);
109     EXPECT_TRUE(cracked_inner_id.empty());
110   }
111 
112   // Make sure GetRegisteredPath returns false for id_ since it is
113   // registered for dragged files.
114   base::FilePath path;
115   ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path));
116 
117   // Deref the current one and registering a new one.
118   isolated_context()->RemoveReference(id_);
119 
120   IsolatedContext::ScopedFSHandle fs2 =
121       isolated_context()->RegisterFileSystemForPath(
122           kFileSystemTypeNativeLocal, std::string(),
123           base::FilePath(DRIVE FPL("/foo")), nullptr);
124 
125   // Make sure the GetDraggedFileInfo returns false for both ones.
126   ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(fs2.id(), &toplevels));
127   ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id_, &toplevels));
128 
129   // Make sure the GetRegisteredPath returns true only for the new one.
130   ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path));
131   ASSERT_TRUE(isolated_context()->GetRegisteredPath(fs2.id(), &path));
132 
133   // Try registering three more file systems for the same path as id2.
134   IsolatedContext::ScopedFSHandle fs3 =
135       isolated_context()->RegisterFileSystemForPath(
136           kFileSystemTypeNativeLocal, std::string(), path, nullptr);
137   IsolatedContext::ScopedFSHandle fs4 =
138       isolated_context()->RegisterFileSystemForPath(
139           kFileSystemTypeNativeLocal, std::string(), path, nullptr);
140   IsolatedContext::ScopedFSHandle fs5 =
141       isolated_context()->RegisterFileSystemForPath(
142           kFileSystemTypeNativeLocal, std::string(), path, nullptr);
143 
144   // Remove file system for id4.
145   fs4 = IsolatedContext::ScopedFSHandle();
146 
147   // Only id4 should become invalid now.
148   ASSERT_TRUE(isolated_context()->GetRegisteredPath(fs2.id(), &path));
149   ASSERT_TRUE(isolated_context()->GetRegisteredPath(fs3.id(), &path));
150   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs4.id(), &path));
151   ASSERT_TRUE(isolated_context()->GetRegisteredPath(fs5.id(), &path));
152 
153   // Revoke file system id5, after adding multiple references.
154   isolated_context()->AddReference(fs5.id());
155   isolated_context()->AddReference(fs5.id());
156   isolated_context()->AddReference(fs5.id());
157   isolated_context()->RevokeFileSystem(fs5.id());
158 
159   // No matter how many references we add id5 must be invalid now.
160   ASSERT_TRUE(isolated_context()->GetRegisteredPath(fs2.id(), &path));
161   ASSERT_TRUE(isolated_context()->GetRegisteredPath(fs3.id(), &path));
162   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs4.id(), &path));
163   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs5.id(), &path));
164 
165   // Revoke the file systems by path.
166   isolated_context()->RevokeFileSystemByPath(path);
167 
168   // Now all the file systems associated to the path must be invalid.
169   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs2.id(), &path));
170   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs3.id(), &path));
171   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs4.id(), &path));
172   ASSERT_FALSE(isolated_context()->GetRegisteredPath(fs5.id(), &path));
173 }
174 
TEST_F(IsolatedContextTest,CrackWithRelativePaths)175 TEST_F(IsolatedContextTest, CrackWithRelativePaths) {
176   const struct {
177     base::FilePath::StringType path;
178     bool valid;
179   } relatives[] = {
180     {FPL("foo"), true},
181     {FPL("foo/bar"), true},
182     {FPL(".."), false},
183     {FPL("foo/.."), false},
184     {FPL("foo/../bar"), false},
185 #if defined(FILE_PATH_USES_WIN_SEPARATORS)
186 #define SHOULD_FAIL_WITH_WIN_SEPARATORS false
187 #else
188 #define SHOULD_FAIL_WITH_WIN_SEPARATORS true
189 #endif
190     {FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS},
191     {FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS},
192   };
193 
194   for (size_t i = 0; i < base::size(kTestPaths); ++i) {
195     for (size_t j = 0; j < base::size(relatives); ++j) {
196       SCOPED_TRACE(testing::Message() << "Testing " << kTestPaths[i].value()
197                                       << " " << relatives[j].path);
198       base::FilePath virtual_path = isolated_context()
199                                         ->CreateVirtualRootPath(id_)
200                                         .AppendASCII(names_[i])
201                                         .Append(relatives[j].path);
202       std::string cracked_id;
203       base::FilePath cracked_path;
204       FileSystemType cracked_type;
205       std::string cracked_inner_id;
206       FileSystemMountOption cracked_option;
207       if (!relatives[j].valid) {
208         ASSERT_FALSE(isolated_context()->CrackVirtualPath(
209             virtual_path, &cracked_id, &cracked_type, &cracked_inner_id,
210             &cracked_path, &cracked_option));
211         continue;
212       }
213       ASSERT_TRUE(isolated_context()->CrackVirtualPath(
214           virtual_path, &cracked_id, &cracked_type, &cracked_inner_id,
215           &cracked_path, &cracked_option));
216       ASSERT_EQ(kTestPaths[i]
217                     .Append(relatives[j].path)
218                     .NormalizePathSeparators()
219                     .value(),
220                 cracked_path.value());
221       ASSERT_EQ(id_, cracked_id);
222       ASSERT_EQ(kFileSystemTypeDragged, cracked_type);
223       EXPECT_TRUE(cracked_inner_id.empty());
224     }
225   }
226 }
227 
TEST_F(IsolatedContextTest,CrackURLWithRelativePaths)228 TEST_F(IsolatedContextTest, CrackURLWithRelativePaths) {
229   const struct {
230     base::FilePath::StringType path;
231     bool valid;
232   } relatives[] = {
233     {FPL("foo"), true},
234     {FPL("foo/bar"), true},
235     {FPL(".."), false},
236     {FPL("foo/.."), false},
237     {FPL("foo/../bar"), false},
238 #if defined(FILE_PATH_USES_WIN_SEPARATORS)
239 #define SHOULD_FAIL_WITH_WIN_SEPARATORS false
240 #else
241 #define SHOULD_FAIL_WITH_WIN_SEPARATORS true
242 #endif
243     {FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS},
244     {FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS},
245   };
246 
247   for (size_t i = 0; i < base::size(kTestPaths); ++i) {
248     for (size_t j = 0; j < base::size(relatives); ++j) {
249       SCOPED_TRACE(testing::Message() << "Testing " << kTestPaths[i].value()
250                                       << " " << relatives[j].path);
251       base::FilePath virtual_path = isolated_context()
252                                         ->CreateVirtualRootPath(id_)
253                                         .AppendASCII(names_[i])
254                                         .Append(relatives[j].path);
255 
256       FileSystemURL cracked = isolated_context()->CreateCrackedFileSystemURL(
257           url::Origin::Create(GURL("http://chromium.org")),
258           kFileSystemTypeIsolated, virtual_path);
259 
260       ASSERT_EQ(relatives[j].valid, cracked.is_valid());
261 
262       if (!relatives[j].valid)
263         continue;
264       ASSERT_EQ("http://chromium.org", cracked.origin().Serialize());
265       ASSERT_EQ(kTestPaths[i]
266                     .Append(relatives[j].path)
267                     .NormalizePathSeparators()
268                     .value(),
269                 cracked.path().value());
270       ASSERT_EQ(virtual_path.NormalizePathSeparators(), cracked.virtual_path());
271       ASSERT_EQ(id_, cracked.filesystem_id());
272       ASSERT_EQ(kFileSystemTypeDragged, cracked.type());
273       ASSERT_EQ(kFileSystemTypeIsolated, cracked.mount_type());
274     }
275   }
276 }
277 
TEST_F(IsolatedContextTest,TestWithVirtualRoot)278 TEST_F(IsolatedContextTest, TestWithVirtualRoot) {
279   std::string cracked_id;
280   base::FilePath cracked_path;
281   FileSystemMountOption cracked_option;
282 
283   // Trying to crack virtual root "/" returns true but with empty cracked path
284   // as "/" of the isolated filesystem is a pure virtual directory
285   // that has no corresponding platform directory.
286   base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_);
287   ASSERT_TRUE(isolated_context()->CrackVirtualPath(
288       virtual_path, &cracked_id, nullptr, nullptr, &cracked_path,
289       &cracked_option));
290   ASSERT_EQ(FPL(""), cracked_path.value());
291   ASSERT_EQ(id_, cracked_id);
292 
293   // Trying to crack "/foo" should fail (because "foo" is not the one
294   // included in the kTestPaths).
295   virtual_path =
296       isolated_context()->CreateVirtualRootPath(id_).AppendASCII("foo");
297   ASSERT_FALSE(isolated_context()->CrackVirtualPath(
298       virtual_path, &cracked_id, nullptr, nullptr, &cracked_path,
299       &cracked_option));
300 }
301 
TEST_F(IsolatedContextTest,CanHandleURL)302 TEST_F(IsolatedContextTest, CanHandleURL) {
303   const GURL test_origin("http://chromium.org");
304   const base::FilePath test_path(FPL("/mount"));
305 
306   // Should handle isolated file system.
307   EXPECT_TRUE(
308       isolated_context()->HandlesFileSystemMountType(kFileSystemTypeIsolated));
309 
310   // Shouldn't handle the rest.
311   EXPECT_FALSE(
312       isolated_context()->HandlesFileSystemMountType(kFileSystemTypeExternal));
313   EXPECT_FALSE(
314       isolated_context()->HandlesFileSystemMountType(kFileSystemTypeTemporary));
315   EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
316       kFileSystemTypePersistent));
317   EXPECT_FALSE(
318       isolated_context()->HandlesFileSystemMountType(kFileSystemTypeTest));
319   // Not even if it's isolated subtype.
320   EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
321       kFileSystemTypeNativeLocal));
322   EXPECT_FALSE(
323       isolated_context()->HandlesFileSystemMountType(kFileSystemTypeDragged));
324   EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
325       kFileSystemTypeNativeMedia));
326   EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
327       kFileSystemTypeDeviceMedia));
328 }
329 
TEST_F(IsolatedContextTest,VirtualFileSystemTests)330 TEST_F(IsolatedContextTest, VirtualFileSystemTests) {
331   // Should be able to register empty and non-absolute paths
332   std::string empty_fsid = isolated_context()->RegisterFileSystemForVirtualPath(
333       kFileSystemTypeIsolated, "_", base::FilePath());
334   std::string relative_fsid =
335       isolated_context()->RegisterFileSystemForVirtualPath(
336           kFileSystemTypeIsolated, "_", base::FilePath(FPL("relpath")));
337   ASSERT_FALSE(empty_fsid.empty());
338   ASSERT_FALSE(relative_fsid.empty());
339 
340   // Make sure that filesystem root is not prepended to cracked virtual paths.
341   base::FilePath database_root = base::FilePath(DRIVE FPL("/database_path"));
342   std::string database_fsid =
343       isolated_context()->RegisterFileSystemForVirtualPath(
344           kFileSystemTypeIsolated, "_", database_root);
345 
346   base::FilePath test_virtual_path =
347       base::FilePath().AppendASCII("virtualdir").AppendASCII("virtualfile.txt");
348 
349   base::FilePath whole_virtual_path = isolated_context()
350                                           ->CreateVirtualRootPath(database_fsid)
351                                           .AppendASCII("_")
352                                           .Append(test_virtual_path);
353 
354   std::string cracked_id;
355   base::FilePath cracked_path;
356   std::string cracked_inner_id;
357   FileSystemMountOption cracked_option;
358   ASSERT_TRUE(isolated_context()->CrackVirtualPath(
359       whole_virtual_path, &cracked_id, nullptr, &cracked_inner_id,
360       &cracked_path, &cracked_option));
361   ASSERT_EQ(database_fsid, cracked_id);
362   ASSERT_EQ(test_virtual_path, cracked_path);
363   EXPECT_TRUE(cracked_inner_id.empty());
364 }
365 
366 }  // namespace storage
367