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_file_handle_impl.h"
6 
7 #include "base/guid.h"
8 #include "base/logging.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/task/post_task.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 "content/public/browser/browser_task_traits.h"
14 #include "mojo/public/cpp/bindings/pending_remote.h"
15 #include "net/base/mime_util.h"
16 #include "storage/browser/blob/blob_data_builder.h"
17 #include "storage/browser/blob/blob_impl.h"
18 #include "storage/browser/blob/blob_storage_context.h"
19 #include "storage/browser/file_system/file_system_operation_runner.h"
20 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
21 #include "third_party/blink/public/mojom/blob/serialized_blob.mojom.h"
22 #include "third_party/blink/public/mojom/native_file_system/native_file_system_error.mojom.h"
23 #include "third_party/blink/public/mojom/native_file_system/native_file_system_transfer_token.mojom.h"
24 
25 using blink::mojom::NativeFileSystemStatus;
26 using storage::BlobDataHandle;
27 using storage::BlobImpl;
28 using storage::FileSystemOperation;
29 using storage::FileSystemOperationRunner;
30 using storage::IsolatedContext;
31 
32 namespace content {
33 
NativeFileSystemFileHandleImpl(NativeFileSystemManagerImpl * manager,const BindingContext & context,const storage::FileSystemURL & url,const SharedHandleState & handle_state)34 NativeFileSystemFileHandleImpl::NativeFileSystemFileHandleImpl(
35     NativeFileSystemManagerImpl* manager,
36     const BindingContext& context,
37     const storage::FileSystemURL& url,
38     const SharedHandleState& handle_state)
39     : NativeFileSystemHandleBase(manager,
40                                  context,
41                                  url,
42                                  handle_state,
43                                  /*is_directory=*/false) {}
44 
45 NativeFileSystemFileHandleImpl::~NativeFileSystemFileHandleImpl() = default;
46 
GetPermissionStatus(bool writable,GetPermissionStatusCallback callback)47 void NativeFileSystemFileHandleImpl::GetPermissionStatus(
48     bool writable,
49     GetPermissionStatusCallback callback) {
50   DoGetPermissionStatus(writable, std::move(callback));
51 }
52 
RequestPermission(bool writable,RequestPermissionCallback callback)53 void NativeFileSystemFileHandleImpl::RequestPermission(
54     bool writable,
55     RequestPermissionCallback callback) {
56   DoRequestPermission(writable, std::move(callback));
57 }
58 
AsBlob(AsBlobCallback callback)59 void NativeFileSystemFileHandleImpl::AsBlob(AsBlobCallback callback) {
60   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
61 
62   if (GetReadPermissionStatus() != PermissionStatus::GRANTED) {
63     std::move(callback).Run(native_file_system_error::FromStatus(
64                                 NativeFileSystemStatus::kPermissionDenied),
65                             base::File::Info(), nullptr);
66     return;
67   }
68 
69   // TODO(mek): Check backend::SupportsStreaming and create snapshot file if
70   // streaming is not supported.
71   DoFileSystemOperation(
72       FROM_HERE, &FileSystemOperationRunner::GetMetadata,
73       base::BindOnce(&NativeFileSystemFileHandleImpl::DidGetMetaDataForBlob,
74                      weak_factory_.GetWeakPtr(), std::move(callback)),
75       url(),
76       FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
77           FileSystemOperation::GET_METADATA_FIELD_SIZE |
78           FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED);
79 }
80 
CreateFileWriter(bool keep_existing_data,CreateFileWriterCallback callback)81 void NativeFileSystemFileHandleImpl::CreateFileWriter(
82     bool keep_existing_data,
83     CreateFileWriterCallback callback) {
84   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
85 
86   RunWithWritePermission(
87       base::BindOnce(&NativeFileSystemFileHandleImpl::CreateFileWriterImpl,
88                      weak_factory_.GetWeakPtr(), keep_existing_data),
89       base::BindOnce([](CreateFileWriterCallback callback) {
90         std::move(callback).Run(native_file_system_error::FromStatus(
91                                     NativeFileSystemStatus::kPermissionDenied),
92                                 mojo::NullRemote());
93       }),
94       std::move(callback));
95 }
96 
IsSameEntry(mojo::PendingRemote<blink::mojom::NativeFileSystemTransferToken> token,IsSameEntryCallback callback)97 void NativeFileSystemFileHandleImpl::IsSameEntry(
98     mojo::PendingRemote<blink::mojom::NativeFileSystemTransferToken> token,
99     IsSameEntryCallback callback) {
100   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
101 
102   manager()->ResolveTransferToken(
103       std::move(token),
104       base::BindOnce(&NativeFileSystemFileHandleImpl::IsSameEntryImpl,
105                      weak_factory_.GetWeakPtr(), std::move(callback)));
106 }
107 
IsSameEntryImpl(IsSameEntryCallback callback,NativeFileSystemTransferTokenImpl * other)108 void NativeFileSystemFileHandleImpl::IsSameEntryImpl(
109     IsSameEntryCallback callback,
110     NativeFileSystemTransferTokenImpl* other) {
111   if (!other) {
112     std::move(callback).Run(
113         native_file_system_error::FromStatus(
114             blink::mojom::NativeFileSystemStatus::kOperationFailed),
115         false);
116     return;
117   }
118 
119   if (other->type() != NativeFileSystemTransferTokenImpl::HandleType::kFile) {
120     std::move(callback).Run(native_file_system_error::Ok(), false);
121     return;
122   }
123 
124   const storage::FileSystemURL& url1 = url();
125   const storage::FileSystemURL& url2 = other->url();
126 
127   // If two URLs are of a different type they are definitely not related.
128   if (url1.type() != url2.type()) {
129     std::move(callback).Run(native_file_system_error::Ok(), false);
130     return;
131   }
132 
133   // Otherwise compare path.
134   const base::FilePath& path1 = url1.path();
135   const base::FilePath& path2 = url2.path();
136   std::move(callback).Run(native_file_system_error::Ok(), path1 == path2);
137 }
138 
Transfer(mojo::PendingReceiver<blink::mojom::NativeFileSystemTransferToken> token)139 void NativeFileSystemFileHandleImpl::Transfer(
140     mojo::PendingReceiver<blink::mojom::NativeFileSystemTransferToken> token) {
141   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
142 
143   manager()->CreateTransferToken(*this, std::move(token));
144 }
145 
146 namespace {
147 
CreateBlobOnIOThread(scoped_refptr<storage::FileSystemContext> file_system_context,const scoped_refptr<ChromeBlobStorageContext> & blob_context,mojo::PendingReceiver<blink::mojom::Blob> blob_receiver,const storage::FileSystemURL & url,const std::string & blob_uuid,const std::string & content_type,const base::File::Info & info)148 void CreateBlobOnIOThread(
149     scoped_refptr<storage::FileSystemContext> file_system_context,
150     const scoped_refptr<ChromeBlobStorageContext>& blob_context,
151     mojo::PendingReceiver<blink::mojom::Blob> blob_receiver,
152     const storage::FileSystemURL& url,
153     const std::string& blob_uuid,
154     const std::string& content_type,
155     const base::File::Info& info) {
156   DCHECK_CURRENTLY_ON(BrowserThread::IO);
157 
158   auto blob_builder = std::make_unique<storage::BlobDataBuilder>(blob_uuid);
159   // Only append if the file has data.
160   if (info.size > 0) {
161     // Use AppendFileSystemFile here, since we're streaming the file directly
162     // from the file system backend, and the file thus might not actually be
163     // backed by a file on disk.
164     blob_builder->AppendFileSystemFile(url.ToGURL(), 0, info.size,
165                                        info.last_modified,
166                                        std::move(file_system_context));
167   }
168   blob_builder->set_content_type(content_type);
169 
170   std::unique_ptr<BlobDataHandle> blob_handle =
171       blob_context->context()->AddFinishedBlob(std::move(blob_builder));
172 
173   // Since the blob we're creating doesn't depend on other blobs, and doesn't
174   // require blob memory/disk quota, creating the blob can't fail.
175   DCHECK(!blob_handle->IsBroken());
176 
177   BlobImpl::Create(std::move(blob_handle), std::move(blob_receiver));
178 }
179 
180 }  // namespace
181 
DidGetMetaDataForBlob(AsBlobCallback callback,base::File::Error result,const base::File::Info & info)182 void NativeFileSystemFileHandleImpl::DidGetMetaDataForBlob(
183     AsBlobCallback callback,
184     base::File::Error result,
185     const base::File::Info& info) {
186   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
187 
188   if (result != base::File::FILE_OK) {
189     std::move(callback).Run(native_file_system_error::FromFileError(result),
190                             base::File::Info(), nullptr);
191     return;
192   }
193 
194   std::string uuid = base::GenerateGUID();
195   std::string content_type;
196 
197   base::FilePath::StringType extension = url().path().Extension();
198   if (!extension.empty()) {
199     std::string mime_type;
200     // TODO(https://crbug.com/962306): Using GetMimeTypeFromExtension and
201     // including platform defined mime type mappings might be nice/make sense,
202     // however that method can potentially block and thus can't be called from
203     // the IO thread.
204     if (net::GetWellKnownMimeTypeFromExtension(extension.substr(1), &mime_type))
205       content_type = std::move(mime_type);
206   }
207   // TODO(https://crbug.com/962306): Consider some kind of fallback type when
208   // the above mime type detection fails.
209 
210   mojo::PendingRemote<blink::mojom::Blob> blob_remote;
211   mojo::PendingReceiver<blink::mojom::Blob> blob_receiver =
212       blob_remote.InitWithNewPipeAndPassReceiver();
213 
214   std::move(callback).Run(
215       native_file_system_error::Ok(), info,
216       blink::mojom::SerializedBlob::New(uuid, content_type, info.size,
217                                         std::move(blob_remote)));
218 
219   base::PostTask(
220       FROM_HERE, {BrowserThread::IO},
221       base::BindOnce(&CreateBlobOnIOThread,
222                      base::WrapRefCounted(file_system_context()),
223                      base::WrapRefCounted(manager()->blob_context()),
224                      std::move(blob_receiver), url(), std::move(uuid),
225                      std::move(content_type), info));
226 }
227 
CreateFileWriterImpl(bool keep_existing_data,CreateFileWriterCallback callback)228 void NativeFileSystemFileHandleImpl::CreateFileWriterImpl(
229     bool keep_existing_data,
230     CreateFileWriterCallback callback) {
231   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
232   DCHECK_EQ(GetWritePermissionStatus(),
233             blink::mojom::PermissionStatus::GRANTED);
234 
235   // We first attempt to create the swap file, even if we might do a subsequent
236   // operation to copy a file to the same path if keep_existing_data is set.
237   // This file creation has to be `exclusive`, meaning, it will fail if a file
238   // already exists. Using the filesystem for synchronization, a successful
239   // creation of the file ensures that this File Writer creation request owns
240   // the file and eliminates possible race conditions.
241   CreateSwapFile(
242       /*count=*/0, keep_existing_data, std::move(callback));
243 }
244 
CreateSwapFile(int count,bool keep_existing_data,CreateFileWriterCallback callback)245 void NativeFileSystemFileHandleImpl::CreateSwapFile(
246     int count,
247     bool keep_existing_data,
248     CreateFileWriterCallback callback) {
249   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
250   DCHECK(count >= 0);
251   DCHECK(max_swap_files_ >= 0);
252 
253   if (GetWritePermissionStatus() != blink::mojom::PermissionStatus::GRANTED) {
254     std::move(callback).Run(native_file_system_error::FromStatus(
255                                 NativeFileSystemStatus::kPermissionDenied),
256                             mojo::NullRemote());
257     return;
258   }
259 
260   auto swap_path =
261       base::FilePath(url().virtual_path()).AddExtensionASCII(".crswap");
262   auto swap_file_system = file_system();
263 
264   if (count >= max_swap_files_) {
265     DLOG(ERROR) << "Error Creating Swap File, count: " << count
266                 << " exceeds max unique files of: " << max_swap_files_
267                 << " base path: " << swap_path;
268     std::move(callback).Run(native_file_system_error::FromStatus(
269                                 NativeFileSystemStatus::kOperationFailed,
270                                 "Failed to create swap file."),
271                             mojo::NullRemote());
272     return;
273   }
274 
275   if (count > 0) {
276     swap_path =
277         swap_path.InsertBeforeExtensionASCII(base::StringPrintf(".%d", count));
278   }
279 
280   // First attempt to just create the swap file in the same file system as this
281   // file.
282   storage::FileSystemURL swap_url =
283       manager()->context()->CreateCrackedFileSystemURL(
284           url().origin(), url().mount_type(), swap_path);
285 
286   // If that failed, it means this file was part of an isolated file system,
287   // and specifically, a single file isolated file system. In that case we'll
288   // need to register a new isolated file system for the swap file, and use that
289   // for the writer.
290   if (!swap_url.is_valid()) {
291     DCHECK_EQ(url().mount_type(), storage::kFileSystemTypeIsolated);
292 
293     swap_path = base::FilePath(url().path()).AddExtensionASCII(".crswap");
294     if (count > 0) {
295       swap_path = swap_path.InsertBeforeExtensionASCII(
296           base::StringPrintf(".%d", count));
297     }
298 
299     auto handle =
300         manager()->CreateFileSystemURLFromPath(context().origin, swap_path);
301     swap_url = std::move(handle.url);
302     swap_file_system = std::move(handle.file_system);
303   }
304 
305   DoFileSystemOperation(
306       FROM_HERE, &FileSystemOperationRunner::CreateFile,
307       base::BindOnce(&NativeFileSystemFileHandleImpl::DidCreateSwapFile,
308                      weak_factory_.GetWeakPtr(), count, swap_url,
309                      swap_file_system, keep_existing_data, std::move(callback)),
310       swap_url,
311       /*exclusive=*/true);
312 }
313 
DidCreateSwapFile(int count,const storage::FileSystemURL & swap_url,storage::IsolatedContext::ScopedFSHandle swap_file_system,bool keep_existing_data,CreateFileWriterCallback callback,base::File::Error result)314 void NativeFileSystemFileHandleImpl::DidCreateSwapFile(
315     int count,
316     const storage::FileSystemURL& swap_url,
317     storage::IsolatedContext::ScopedFSHandle swap_file_system,
318     bool keep_existing_data,
319     CreateFileWriterCallback callback,
320     base::File::Error result) {
321   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
322   if (result == base::File::FILE_ERROR_EXISTS) {
323     // Creation attempt failed. We need to find an unused filename.
324     CreateSwapFile(count + 1, keep_existing_data, std::move(callback));
325     return;
326   }
327 
328   if (result != base::File::FILE_OK) {
329     DLOG(ERROR) << "Error Creating Swap File, status: "
330                 << base::File::ErrorToString(result)
331                 << " path: " << swap_url.path();
332     std::move(callback).Run(native_file_system_error::FromFileError(
333                                 result, "Error creating swap file."),
334                             mojo::NullRemote());
335     return;
336   }
337 
338   if (!keep_existing_data) {
339     std::move(callback).Run(
340         native_file_system_error::Ok(),
341         manager()->CreateFileWriter(
342             context(), url(), swap_url,
343             NativeFileSystemManagerImpl::SharedHandleState(
344                 handle_state().read_grant, handle_state().write_grant,
345                 swap_file_system)));
346     return;
347   }
348 
349   DoFileSystemOperation(
350       FROM_HERE, &FileSystemOperationRunner::Copy,
351       base::BindOnce(&NativeFileSystemFileHandleImpl::DidCopySwapFile,
352                      weak_factory_.GetWeakPtr(), swap_url, swap_file_system,
353                      std::move(callback)),
354       url(), swap_url,
355       storage::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED,
356       storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
357       storage::FileSystemOperation::CopyProgressCallback());
358 }
359 
DidCopySwapFile(const storage::FileSystemURL & swap_url,storage::IsolatedContext::ScopedFSHandle swap_file_system,CreateFileWriterCallback callback,base::File::Error result)360 void NativeFileSystemFileHandleImpl::DidCopySwapFile(
361     const storage::FileSystemURL& swap_url,
362     storage::IsolatedContext::ScopedFSHandle swap_file_system,
363     CreateFileWriterCallback callback,
364     base::File::Error result) {
365   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
366   if (result != base::File::FILE_OK) {
367     DLOG(ERROR) << "Error Creating Swap File, status: "
368                 << base::File::ErrorToString(result)
369                 << " path: " << swap_url.path();
370     std::move(callback).Run(native_file_system_error::FromFileError(
371                                 result, "Error copying to swap file."),
372                             mojo::NullRemote());
373     return;
374   }
375   std::move(callback).Run(
376       native_file_system_error::Ok(),
377       manager()->CreateFileWriter(
378           context(), url(), swap_url,
379           NativeFileSystemManagerImpl::SharedHandleState(
380               handle_state().read_grant, handle_state().write_grant,
381               swap_file_system)));
382 }
383 
384 base::WeakPtr<NativeFileSystemHandleBase>
AsWeakPtr()385 NativeFileSystemFileHandleImpl::AsWeakPtr() {
386   return weak_factory_.GetWeakPtr();
387 }
388 
389 }  // namespace content
390