1 // Copyright 2019 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 "content/browser/native_file_system/native_file_system_directory_handle_impl.h"
6 
7 #include "base/strings/strcat.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "build/build_config.h"
11 #include "content/browser/native_file_system/native_file_system_error.h"
12 #include "content/browser/native_file_system/native_file_system_transfer_token_impl.h"
13 #include "mojo/public/cpp/bindings/pending_remote.h"
14 #include "net/base/escape.h"
15 #include "storage/browser/file_system/file_system_context.h"
16 #include "storage/browser/file_system/file_system_operation_runner.h"
17 #include "storage/common/file_system/file_system_util.h"
18 #include "third_party/blink/public/mojom/native_file_system/native_file_system_error.mojom.h"
19 #include "third_party/blink/public/mojom/native_file_system/native_file_system_file_handle.mojom.h"
20 #include "third_party/blink/public/mojom/native_file_system/native_file_system_transfer_token.mojom.h"
21 
22 using blink::mojom::NativeFileSystemEntry;
23 using blink::mojom::NativeFileSystemEntryPtr;
24 using blink::mojom::NativeFileSystemHandle;
25 using blink::mojom::NativeFileSystemStatus;
26 using blink::mojom::NativeFileSystemTransferToken;
27 using storage::FileSystemOperationRunner;
28 
29 namespace content {
30 
31 namespace {
32 
33 // Returns true when |name| contains a path separator like "/".
ContainsPathSeparator(const std::string & name)34 bool ContainsPathSeparator(const std::string& name) {
35   const base::FilePath filepath_name = storage::StringToFilePath(name);
36 
37   const size_t separator_position =
38       filepath_name.value().find_first_of(base::FilePath::kSeparators);
39 
40   return separator_position != base::FilePath::StringType::npos;
41 }
42 
43 // Returns true when |name| is "." or "..".
IsCurrentOrParentDirectory(const std::string & name)44 bool IsCurrentOrParentDirectory(const std::string& name) {
45   const base::FilePath filepath_name = storage::StringToFilePath(name);
46   return filepath_name.value() == base::FilePath::kCurrentDirectory ||
47          filepath_name.value() == base::FilePath::kParentDirectory;
48 }
49 
50 }  // namespace
51 
NativeFileSystemDirectoryHandleImpl(NativeFileSystemManagerImpl * manager,const BindingContext & context,const storage::FileSystemURL & url,const SharedHandleState & handle_state)52 NativeFileSystemDirectoryHandleImpl::NativeFileSystemDirectoryHandleImpl(
53     NativeFileSystemManagerImpl* manager,
54     const BindingContext& context,
55     const storage::FileSystemURL& url,
56     const SharedHandleState& handle_state)
57     : NativeFileSystemHandleBase(manager,
58                                  context,
59                                  url,
60                                  handle_state,
61                                  /*is_directory=*/true) {}
62 
63 NativeFileSystemDirectoryHandleImpl::~NativeFileSystemDirectoryHandleImpl() =
64     default;
65 
GetPermissionStatus(bool writable,GetPermissionStatusCallback callback)66 void NativeFileSystemDirectoryHandleImpl::GetPermissionStatus(
67     bool writable,
68     GetPermissionStatusCallback callback) {
69   DoGetPermissionStatus(writable, std::move(callback));
70 }
71 
RequestPermission(bool writable,RequestPermissionCallback callback)72 void NativeFileSystemDirectoryHandleImpl::RequestPermission(
73     bool writable,
74     RequestPermissionCallback callback) {
75   DoRequestPermission(writable, std::move(callback));
76 }
77 
GetFile(const std::string & basename,bool create,GetFileCallback callback)78 void NativeFileSystemDirectoryHandleImpl::GetFile(const std::string& basename,
79                                                   bool create,
80                                                   GetFileCallback callback) {
81   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
82 
83   storage::FileSystemURL child_url;
84   blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
85       GetChildURL(basename, &child_url);
86   if (get_child_url_result->status != NativeFileSystemStatus::kOk) {
87     std::move(callback).Run(std::move(get_child_url_result),
88                             mojo::NullRemote());
89     return;
90   }
91 
92   if (GetReadPermissionStatus() != PermissionStatus::GRANTED) {
93     std::move(callback).Run(native_file_system_error::FromStatus(
94                                 NativeFileSystemStatus::kPermissionDenied),
95                             mojo::NullRemote());
96     return;
97   }
98 
99   if (create) {
100     // If |create| is true, write permission is required unconditionally, i.e.
101     // even if the file already exists. This is intentional, and matches the
102     // behavior that is specified in the spec.
103     RunWithWritePermission(
104         base::BindOnce(
105             &NativeFileSystemDirectoryHandleImpl::GetFileWithWritePermission,
106             weak_factory_.GetWeakPtr(), child_url),
107         base::BindOnce([](GetFileCallback callback) {
108           std::move(callback).Run(
109               native_file_system_error::FromStatus(
110                   NativeFileSystemStatus::kPermissionDenied),
111               mojo::NullRemote());
112         }),
113         std::move(callback));
114   } else {
115     DoFileSystemOperation(
116         FROM_HERE, &FileSystemOperationRunner::FileExists,
117         base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetFile,
118                        weak_factory_.GetWeakPtr(), child_url,
119                        std::move(callback)),
120         child_url);
121   }
122 }
123 
GetDirectory(const std::string & basename,bool create,GetDirectoryCallback callback)124 void NativeFileSystemDirectoryHandleImpl::GetDirectory(
125     const std::string& basename,
126     bool create,
127     GetDirectoryCallback callback) {
128   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
129 
130   storage::FileSystemURL child_url;
131   blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
132       GetChildURL(basename, &child_url);
133   if (get_child_url_result->status != NativeFileSystemStatus::kOk) {
134     std::move(callback).Run(std::move(get_child_url_result),
135                             mojo::NullRemote());
136     return;
137   }
138 
139   if (GetReadPermissionStatus() != PermissionStatus::GRANTED) {
140     std::move(callback).Run(native_file_system_error::FromStatus(
141                                 NativeFileSystemStatus::kPermissionDenied),
142                             mojo::NullRemote());
143     return;
144   }
145 
146   if (create) {
147     // If |create| is true, write permission is required unconditionally, i.e.
148     // even if the file already exists. This is intentional, and matches the
149     // behavior that is specified in the spec.
150     RunWithWritePermission(
151         base::BindOnce(&NativeFileSystemDirectoryHandleImpl::
152                            GetDirectoryWithWritePermission,
153                        weak_factory_.GetWeakPtr(), child_url),
154         base::BindOnce([](GetDirectoryCallback callback) {
155           std::move(callback).Run(
156               native_file_system_error::FromStatus(
157                   NativeFileSystemStatus::kPermissionDenied),
158               mojo::NullRemote());
159         }),
160         std::move(callback));
161   } else {
162     DoFileSystemOperation(
163         FROM_HERE, &FileSystemOperationRunner::DirectoryExists,
164         base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetDirectory,
165                        weak_factory_.GetWeakPtr(), child_url,
166                        std::move(callback)),
167         child_url);
168   }
169 }
170 
GetEntries(mojo::PendingRemote<blink::mojom::NativeFileSystemDirectoryEntriesListener> pending_listener)171 void NativeFileSystemDirectoryHandleImpl::GetEntries(
172     mojo::PendingRemote<blink::mojom::NativeFileSystemDirectoryEntriesListener>
173         pending_listener) {
174   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
175 
176   std::unique_ptr<
177       mojo::Remote<blink::mojom::NativeFileSystemDirectoryEntriesListener>,
178       base::OnTaskRunnerDeleter>
179       listener(
180           new mojo::Remote<
181               blink::mojom::NativeFileSystemDirectoryEntriesListener>(
182               std::move(pending_listener)),
183           base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
184   listener->reset_on_disconnect();
185 
186   DoFileSystemOperation(
187       FROM_HERE, &FileSystemOperationRunner::ReadDirectory,
188       base::BindRepeating(
189           &NativeFileSystemDirectoryHandleImpl::DidReadDirectory,
190           weak_factory_.GetWeakPtr(), base::Owned(std::move(listener))),
191       url());
192 }
193 
RemoveEntry(const std::string & basename,bool recurse,RemoveEntryCallback callback)194 void NativeFileSystemDirectoryHandleImpl::RemoveEntry(
195     const std::string& basename,
196     bool recurse,
197     RemoveEntryCallback callback) {
198   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
199 
200   storage::FileSystemURL child_url;
201   blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
202       GetChildURL(basename, &child_url);
203   if (get_child_url_result->status != NativeFileSystemStatus::kOk) {
204     std::move(callback).Run(std::move(get_child_url_result));
205     return;
206   }
207 
208   RunWithWritePermission(
209       base::BindOnce(&NativeFileSystemDirectoryHandleImpl::RemoveEntryImpl,
210                      weak_factory_.GetWeakPtr(), child_url, recurse),
211       base::BindOnce([](RemoveEntryCallback callback) {
212         std::move(callback).Run(native_file_system_error::FromStatus(
213             NativeFileSystemStatus::kPermissionDenied));
214       }),
215       std::move(callback));
216 }
Resolve(mojo::PendingRemote<blink::mojom::NativeFileSystemTransferToken> possible_child,ResolveCallback callback)217 void NativeFileSystemDirectoryHandleImpl::Resolve(
218     mojo::PendingRemote<blink::mojom::NativeFileSystemTransferToken>
219         possible_child,
220     ResolveCallback callback) {
221   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
222 
223   manager()->ResolveTransferToken(
224       std::move(possible_child),
225       base::BindOnce(&NativeFileSystemDirectoryHandleImpl::ResolveImpl,
226                      weak_factory_.GetWeakPtr(), std::move(callback)));
227 }
228 
ResolveImpl(ResolveCallback callback,NativeFileSystemTransferTokenImpl * possible_child)229 void NativeFileSystemDirectoryHandleImpl::ResolveImpl(
230     ResolveCallback callback,
231     NativeFileSystemTransferTokenImpl* possible_child) {
232   if (!possible_child) {
233     std::move(callback).Run(
234         native_file_system_error::FromStatus(
235             blink::mojom::NativeFileSystemStatus::kOperationFailed),
236         base::nullopt);
237     return;
238   }
239 
240   const storage::FileSystemURL& parent_url = url();
241   const storage::FileSystemURL& child_url = possible_child->url();
242 
243   // If two URLs are of a different type they are definitely not related.
244   if (parent_url.type() != child_url.type()) {
245     std::move(callback).Run(native_file_system_error::Ok(), base::nullopt);
246     return;
247   }
248 
249   // Otherwise compare path.
250   const base::FilePath& parent_path = parent_url.path();
251   const base::FilePath& child_path = child_url.path();
252 
253   // Same path, so return empty array if child is also a directory.
254   if (parent_path == child_path) {
255     std::move(callback).Run(
256         native_file_system_error::Ok(),
257         possible_child->type() ==
258                 NativeFileSystemTransferTokenImpl::HandleType::kDirectory
259             ? base::make_optional(std::vector<std::string>())
260             : base::nullopt);
261     return;
262   }
263 
264   // Now figure out relative path, if any.
265   base::FilePath relative_path;
266   if (parent_path.empty()) {
267     // The root of a sandboxed file system will have an empty path. In that
268     // case the child path is already the relative path.
269     relative_path = child_path;
270   } else if (!parent_path.AppendRelativePath(child_path, &relative_path)) {
271     std::move(callback).Run(native_file_system_error::Ok(), base::nullopt);
272     return;
273   }
274 
275   std::vector<base::FilePath::StringType> components;
276   relative_path.GetComponents(&components);
277 #if defined(OS_WIN)
278   std::vector<std::string> result;
279   result.reserve(components.size());
280   for (const auto& component : components) {
281     result.push_back(base::UTF16ToUTF8(component));
282   }
283   std::move(callback).Run(native_file_system_error::Ok(), std::move(result));
284 #else
285   std::move(callback).Run(native_file_system_error::Ok(),
286                           std::move(components));
287 #endif
288 }
289 
Transfer(mojo::PendingReceiver<NativeFileSystemTransferToken> token)290 void NativeFileSystemDirectoryHandleImpl::Transfer(
291     mojo::PendingReceiver<NativeFileSystemTransferToken> token) {
292   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
293 
294   manager()->CreateTransferToken(*this, std::move(token));
295 }
296 
GetFileWithWritePermission(const storage::FileSystemURL & child_url,GetFileCallback callback)297 void NativeFileSystemDirectoryHandleImpl::GetFileWithWritePermission(
298     const storage::FileSystemURL& child_url,
299     GetFileCallback callback) {
300   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
301   DCHECK_EQ(GetWritePermissionStatus(),
302             blink::mojom::PermissionStatus::GRANTED);
303 
304   DoFileSystemOperation(
305       FROM_HERE, &FileSystemOperationRunner::CreateFile,
306       base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetFile,
307                      weak_factory_.GetWeakPtr(), child_url,
308                      std::move(callback)),
309       child_url,
310       /*exclusive=*/false);
311 }
312 
DidGetFile(const storage::FileSystemURL & url,GetFileCallback callback,base::File::Error result)313 void NativeFileSystemDirectoryHandleImpl::DidGetFile(
314     const storage::FileSystemURL& url,
315     GetFileCallback callback,
316     base::File::Error result) {
317   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
318 
319   if (result != base::File::FILE_OK) {
320     std::move(callback).Run(native_file_system_error::FromFileError(result),
321                             mojo::NullRemote());
322     return;
323   }
324 
325   std::move(callback).Run(
326       native_file_system_error::Ok(),
327       manager()->CreateFileHandle(context(), url, handle_state()));
328 }
329 
GetDirectoryWithWritePermission(const storage::FileSystemURL & child_url,GetDirectoryCallback callback)330 void NativeFileSystemDirectoryHandleImpl::GetDirectoryWithWritePermission(
331     const storage::FileSystemURL& child_url,
332     GetDirectoryCallback callback) {
333   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
334   DCHECK_EQ(GetWritePermissionStatus(),
335             blink::mojom::PermissionStatus::GRANTED);
336 
337   DoFileSystemOperation(
338       FROM_HERE, &FileSystemOperationRunner::CreateDirectory,
339       base::BindOnce(&NativeFileSystemDirectoryHandleImpl::DidGetDirectory,
340                      weak_factory_.GetWeakPtr(), child_url,
341                      std::move(callback)),
342       child_url,
343       /*exclusive=*/false, /*recursive=*/false);
344 }
345 
DidGetDirectory(const storage::FileSystemURL & url,GetDirectoryCallback callback,base::File::Error result)346 void NativeFileSystemDirectoryHandleImpl::DidGetDirectory(
347     const storage::FileSystemURL& url,
348     GetDirectoryCallback callback,
349     base::File::Error result) {
350   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
351 
352   if (result != base::File::FILE_OK) {
353     std::move(callback).Run(native_file_system_error::FromFileError(result),
354                             mojo::NullRemote());
355     return;
356   }
357 
358   std::move(callback).Run(
359       native_file_system_error::Ok(),
360       manager()->CreateDirectoryHandle(context(), url, handle_state()));
361 }
362 
DidReadDirectory(mojo::Remote<blink::mojom::NativeFileSystemDirectoryEntriesListener> * listener,base::File::Error result,std::vector<filesystem::mojom::DirectoryEntry> file_list,bool has_more_entries)363 void NativeFileSystemDirectoryHandleImpl::DidReadDirectory(
364     mojo::Remote<blink::mojom::NativeFileSystemDirectoryEntriesListener>*
365         listener,
366     base::File::Error result,
367     std::vector<filesystem::mojom::DirectoryEntry> file_list,
368     bool has_more_entries) {
369   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
370 
371   if (!*listener)
372     return;
373 
374   if (result != base::File::FILE_OK) {
375     DCHECK(!has_more_entries);
376     (*listener)->DidReadDirectory(
377         native_file_system_error::FromFileError(result), {}, false);
378     return;
379   }
380 
381   std::vector<NativeFileSystemEntryPtr> entries;
382   for (const auto& entry : file_list) {
383     std::string basename = storage::FilePathToString(entry.name);
384 
385     storage::FileSystemURL child_url;
386     blink::mojom::NativeFileSystemErrorPtr get_child_url_result =
387         GetChildURL(basename, &child_url);
388 
389     // All entries must exist in this directory as a direct child with a valid
390     // |basename|.
391     CHECK_EQ(get_child_url_result->status, NativeFileSystemStatus::kOk);
392 
393     entries.push_back(
394         CreateEntry(basename, child_url,
395                     entry.type == filesystem::mojom::FsFileType::DIRECTORY));
396   }
397   (*listener)->DidReadDirectory(native_file_system_error::Ok(),
398                                 std::move(entries), has_more_entries);
399 }
400 
RemoveEntryImpl(const storage::FileSystemURL & url,bool recurse,RemoveEntryCallback callback)401 void NativeFileSystemDirectoryHandleImpl::RemoveEntryImpl(
402     const storage::FileSystemURL& url,
403     bool recurse,
404     RemoveEntryCallback callback) {
405   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
406   DCHECK_EQ(GetWritePermissionStatus(),
407             blink::mojom::PermissionStatus::GRANTED);
408 
409   DoFileSystemOperation(
410       FROM_HERE, &FileSystemOperationRunner::Remove,
411       base::BindOnce(
412           [](RemoveEntryCallback callback, base::File::Error result) {
413             std::move(callback).Run(
414                 native_file_system_error::FromFileError(result));
415           },
416           std::move(callback)),
417       url, recurse);
418 }
419 
420 blink::mojom::NativeFileSystemErrorPtr
GetChildURL(const std::string & basename,storage::FileSystemURL * result)421 NativeFileSystemDirectoryHandleImpl::GetChildURL(
422     const std::string& basename,
423     storage::FileSystemURL* result) {
424   // TODO(mek): Rather than doing URL serialization and parsing we should just
425   // have a way to get a child FileSystemURL directly from its parent.
426 
427   if (basename.empty()) {
428     return native_file_system_error::FromStatus(
429         NativeFileSystemStatus::kInvalidArgument,
430         "Name can't be an empty string.");
431   }
432 
433   if (ContainsPathSeparator(basename) || IsCurrentOrParentDirectory(basename)) {
434     // |basename| must refer to a entry that exists in this directory as a
435     // direct child.
436     return native_file_system_error::FromStatus(
437         NativeFileSystemStatus::kInvalidArgument,
438         "Name contains invalid characters.");
439   }
440 
441   std::string escaped_name =
442       net::EscapeQueryParamValue(basename, /*use_plus=*/false);
443 
444   GURL parent_url = url().ToGURL();
445   std::string path = base::StrCat({parent_url.path(), "/", escaped_name});
446   GURL::Replacements replacements;
447   replacements.SetPathStr(path);
448   GURL child_url = parent_url.ReplaceComponents(replacements);
449 
450   *result = file_system_context()->CrackURL(child_url);
451   return native_file_system_error::Ok();
452 }
453 
CreateEntry(const std::string & basename,const storage::FileSystemURL & url,bool is_directory)454 NativeFileSystemEntryPtr NativeFileSystemDirectoryHandleImpl::CreateEntry(
455     const std::string& basename,
456     const storage::FileSystemURL& url,
457     bool is_directory) {
458   if (is_directory) {
459     return NativeFileSystemEntry::New(
460         NativeFileSystemHandle::NewDirectory(
461             manager()->CreateDirectoryHandle(context(), url, handle_state())),
462         basename);
463   }
464   return NativeFileSystemEntry::New(
465       NativeFileSystemHandle::NewFile(
466           manager()->CreateFileHandle(context(), url, handle_state())),
467       basename);
468 }
469 
470 base::WeakPtr<NativeFileSystemHandleBase>
AsWeakPtr()471 NativeFileSystemDirectoryHandleImpl::AsWeakPtr() {
472   return weak_factory_.GetWeakPtr();
473 }
474 
475 }  // namespace content
476