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