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