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/file_system_access/native_file_system_file_writer_impl.h"
6
7 #include "base/files/file_util.h"
8 #include "base/logging.h"
9 #include "base/task/post_task.h"
10 #include "base/task/thread_pool.h"
11 #include "build/build_config.h"
12 #include "components/services/quarantine/quarantine.h"
13 #include "content/browser/file_system_access/native_file_system_error.h"
14 #include "content/browser/file_system_access/native_file_system_manager_impl.h"
15 #include "content/public/browser/content_browser_client.h"
16 #include "content/public/common/content_client.h"
17 #include "crypto/secure_hash.h"
18 #include "mojo/public/cpp/bindings/callback_helpers.h"
19 #include "storage/browser/blob/blob_storage_context.h"
20 #include "storage/browser/file_system/file_stream_reader.h"
21 #include "storage/browser/file_system/file_system_operation_runner.h"
22 #include "third_party/blink/public/common/blob/blob_utils.h"
23 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
24 #include "third_party/blink/public/mojom/file_system_access/native_file_system_error.mojom.h"
25
26 using blink::mojom::NativeFileSystemStatus;
27 using storage::BlobDataHandle;
28 using storage::FileSystemOperation;
29 using storage::FileSystemOperationRunner;
30
31 namespace content {
32
33 namespace {
34
35 // For after write checks we need the hash and size of the file. That data is
36 // calculated on the IO thread by this class.
37 // This class is ref-counted to make it easier to integrate with the
38 // FileStreamReader API where methods either return synchronously or invoke
39 // their callback asynchronously.
40 class HashCalculator : public base::RefCounted<HashCalculator> {
41 public:
42 // Must be called on the FileSystemContext's IO runner.
CreateAndStart(scoped_refptr<storage::FileSystemContext> context,NativeFileSystemFileWriterImpl::HashCallback callback,const storage::FileSystemURL & swap_url,storage::FileSystemOperationRunner *)43 static void CreateAndStart(
44 scoped_refptr<storage::FileSystemContext> context,
45 NativeFileSystemFileWriterImpl::HashCallback callback,
46 const storage::FileSystemURL& swap_url,
47 storage::FileSystemOperationRunner*) {
48 auto calculator = base::MakeRefCounted<HashCalculator>(std::move(context),
49 std::move(callback));
50 calculator->Start(swap_url);
51 }
52
HashCalculator(scoped_refptr<storage::FileSystemContext> context,NativeFileSystemFileWriterImpl::HashCallback callback)53 HashCalculator(scoped_refptr<storage::FileSystemContext> context,
54 NativeFileSystemFileWriterImpl::HashCallback callback)
55 : context_(std::move(context)), callback_(std::move(callback)) {
56 DCHECK(context_);
57 }
58
59 private:
60 friend class base::RefCounted<HashCalculator>;
61 ~HashCalculator() = default;
62
Start(const storage::FileSystemURL & swap_url)63 void Start(const storage::FileSystemURL& swap_url) {
64 reader_ = context_->CreateFileStreamReader(
65 swap_url, 0, storage::kMaximumLength, base::Time());
66 int64_t length =
67 reader_->GetLength(base::BindOnce(&HashCalculator::GotLength, this));
68 if (length == net::ERR_IO_PENDING)
69 return;
70 GotLength(length);
71 }
72
GotLength(int64_t length)73 void GotLength(int64_t length) {
74 if (length < 0) {
75 std::move(callback_).Run(storage::NetErrorToFileError(length),
76 std::string(), -1);
77 return;
78 }
79
80 file_size_ = length;
81 ReadMore();
82 }
83
ReadMore()84 void ReadMore() {
85 DCHECK_GE(file_size_, 0);
86 int read_result =
87 reader_->Read(buffer_.get(), buffer_->size(),
88 base::BindOnce(&HashCalculator::DidRead, this));
89 if (read_result == net::ERR_IO_PENDING)
90 return;
91 DidRead(read_result);
92 }
93
DidRead(int bytes_read)94 void DidRead(int bytes_read) {
95 DCHECK_GE(file_size_, 0);
96 if (bytes_read < 0) {
97 std::move(callback_).Run(storage::NetErrorToFileError(bytes_read),
98 std::string(), -1);
99 return;
100 }
101 if (bytes_read == 0) {
102 std::string hash_str(hash_->GetHashLength(), 0);
103 hash_->Finish(base::data(hash_str), hash_str.size());
104 std::move(callback_).Run(base::File::FILE_OK, hash_str, file_size_);
105 return;
106 }
107
108 hash_->Update(buffer_->data(), bytes_read);
109 ReadMore();
110 }
111
112 const scoped_refptr<storage::FileSystemContext> context_;
113 NativeFileSystemFileWriterImpl::HashCallback callback_;
114
115 const scoped_refptr<net::IOBufferWithSize> buffer_{
116 base::MakeRefCounted<net::IOBufferWithSize>(8 * 1024)};
117
118 const std::unique_ptr<crypto::SecureHash> hash_{
119 crypto::SecureHash::Create(crypto::SecureHash::SHA256)};
120
121 std::unique_ptr<storage::FileStreamReader> reader_;
122 int64_t file_size_ = -1;
123 };
124
RemoveSwapFile(const storage::FileSystemURL & swap_url,storage::FileSystemOperationRunner * runner)125 void RemoveSwapFile(const storage::FileSystemURL& swap_url,
126 storage::FileSystemOperationRunner* runner) {
127 runner->Remove(swap_url, /*recursive=*/false, base::DoNothing());
128 }
129
130 } // namespace
131
132 struct NativeFileSystemFileWriterImpl::WriteState {
133 WriteCallback callback;
134 uint64_t bytes_written = 0;
135 };
136
NativeFileSystemFileWriterImpl(NativeFileSystemManagerImpl * manager,const BindingContext & context,const storage::FileSystemURL & url,const storage::FileSystemURL & swap_url,const SharedHandleState & handle_state,bool has_transient_user_activation,download::QuarantineConnectionCallback quarantine_connection_callback)137 NativeFileSystemFileWriterImpl::NativeFileSystemFileWriterImpl(
138 NativeFileSystemManagerImpl* manager,
139 const BindingContext& context,
140 const storage::FileSystemURL& url,
141 const storage::FileSystemURL& swap_url,
142 const SharedHandleState& handle_state,
143 bool has_transient_user_activation,
144 download::QuarantineConnectionCallback quarantine_connection_callback)
145 : NativeFileSystemHandleBase(manager, context, url, handle_state),
146 swap_url_(swap_url),
147 quarantine_connection_callback_(
148 std::move(quarantine_connection_callback)),
149 has_transient_user_activation_(has_transient_user_activation) {
150 DCHECK_EQ(swap_url.type(), url.type());
151 }
152
~NativeFileSystemFileWriterImpl()153 NativeFileSystemFileWriterImpl::~NativeFileSystemFileWriterImpl() {
154 if (can_purge()) {
155 DoFileSystemOperation(FROM_HERE, &FileSystemOperationRunner::RemoveFile,
156 base::BindOnce(
157 [](const storage::FileSystemURL& swap_url,
158 base::File::Error result) {
159 if (result != base::File::FILE_OK) {
160 DLOG(ERROR)
161 << "Error Deleting Swap File, status: "
162 << base::File::ErrorToString(result)
163 << " path: " << swap_url.path();
164 }
165 },
166 swap_url()),
167 swap_url());
168 }
169 }
170
Write(uint64_t offset,mojo::PendingRemote<blink::mojom::Blob> data,WriteCallback callback)171 void NativeFileSystemFileWriterImpl::Write(
172 uint64_t offset,
173 mojo::PendingRemote<blink::mojom::Blob> data,
174 WriteCallback callback) {
175 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
176
177 RunWithWritePermission(
178 base::BindOnce(&NativeFileSystemFileWriterImpl::WriteImpl,
179 weak_factory_.GetWeakPtr(), offset, std::move(data)),
180 base::BindOnce([](blink::mojom::NativeFileSystemErrorPtr result,
181 WriteCallback callback) {
182 std::move(callback).Run(std::move(result),
183 /*bytes_written=*/0);
184 }),
185 std::move(callback));
186 }
187
WriteStream(uint64_t offset,mojo::ScopedDataPipeConsumerHandle stream,WriteStreamCallback callback)188 void NativeFileSystemFileWriterImpl::WriteStream(
189 uint64_t offset,
190 mojo::ScopedDataPipeConsumerHandle stream,
191 WriteStreamCallback callback) {
192 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
193
194 RunWithWritePermission(
195 base::BindOnce(&NativeFileSystemFileWriterImpl::WriteStreamImpl,
196 weak_factory_.GetWeakPtr(), offset, std::move(stream)),
197 base::BindOnce([](blink::mojom::NativeFileSystemErrorPtr result,
198 WriteStreamCallback callback) {
199 std::move(callback).Run(std::move(result),
200 /*bytes_written=*/0);
201 }),
202 std::move(callback));
203 }
204
Truncate(uint64_t length,TruncateCallback callback)205 void NativeFileSystemFileWriterImpl::Truncate(uint64_t length,
206 TruncateCallback callback) {
207 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
208
209 RunWithWritePermission(
210 base::BindOnce(&NativeFileSystemFileWriterImpl::TruncateImpl,
211 weak_factory_.GetWeakPtr(), length),
212 base::BindOnce([](blink::mojom::NativeFileSystemErrorPtr result,
213 TruncateCallback callback) {
214 std::move(callback).Run(std::move(result));
215 }),
216 std::move(callback));
217 }
218
Close(CloseCallback callback)219 void NativeFileSystemFileWriterImpl::Close(CloseCallback callback) {
220 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
221
222 RunWithWritePermission(
223 base::BindOnce(&NativeFileSystemFileWriterImpl::CloseImpl,
224 weak_factory_.GetWeakPtr()),
225 base::BindOnce([](blink::mojom::NativeFileSystemErrorPtr result,
226 CloseCallback callback) {
227 std::move(callback).Run(std::move(result));
228 }),
229 std::move(callback));
230 }
231
232 namespace {
233
234 // Writing a blob to a file consists of three operations:
235 // 1) The Blob reads its data, and writes it out to a mojo data pipe producer
236 // handle. It calls BlobReaderClient::OnComplete when all data has been
237 // written.
238 // 2) WriteStream reads data from the associated mojo data pipe consumer handle
239 // as long as data is available.
240 // 3) All the read data is written to disk. Signalled by calling WriteCompleted.
241 //
242 // All of these steps are done in parallel, operating on chunks. Furthermore the
243 // OnComplete call from step 1) is done over a different mojo pipe than where
244 // the data is sent, making it possible for this to arrive either before or
245 // after step 3) completes. To make sure we report an error when any of these
246 // steps fail, this helper class waits for both the OnComplete call to arrive
247 // and for the write to finish before considering the entire write operation a
248 // success.
249 //
250 // On the other hand, as soon as we're aware of any of these steps failing, that
251 // error can be propagated, as that means the operation failed.
252 // In other words, this is like Promise.all, which resolves when all
253 // operations succeed, or rejects as soon as any operation fails.
254 //
255 // This class deletes itself after calling its callback.
256 class BlobReaderClient : public base::SupportsWeakPtr<BlobReaderClient>,
257 public blink::mojom::BlobReaderClient {
258 public:
BlobReaderClient(NativeFileSystemFileWriterImpl::WriteCallback callback,mojo::PendingReceiver<blink::mojom::BlobReaderClient> receiver)259 BlobReaderClient(
260 NativeFileSystemFileWriterImpl::WriteCallback callback,
261 mojo::PendingReceiver<blink::mojom::BlobReaderClient> receiver)
262 : callback_(std::move(callback)), receiver_(this, std::move(receiver)) {
263 receiver_.set_disconnect_handler(
264 base::BindOnce(&BlobReaderClient::OnDisconnect, AsWeakPtr()));
265 }
266
OnCalculatedSize(uint64_t total_size,uint64_t expected_content_size)267 void OnCalculatedSize(uint64_t total_size,
268 uint64_t expected_content_size) override {}
OnComplete(int32_t status,uint64_t data_length)269 void OnComplete(int32_t status, uint64_t data_length) override {
270 DCHECK(!read_result_.has_value());
271 read_result_ = status;
272 MaybeCallCallbackAndDeleteThis();
273 }
274
WriteCompleted(blink::mojom::NativeFileSystemErrorPtr result,uint64_t bytes_written)275 void WriteCompleted(blink::mojom::NativeFileSystemErrorPtr result,
276 uint64_t bytes_written) {
277 DCHECK(!write_result_);
278 write_result_ = std::move(result);
279 bytes_written_ = bytes_written;
280 MaybeCallCallbackAndDeleteThis();
281 }
282
283 private:
284 friend class base::RefCounted<BlobReaderClient>;
285 ~BlobReaderClient() override = default;
286
OnDisconnect()287 void OnDisconnect() {
288 if (!read_result_.has_value()) {
289 // Disconnected without getting a read result, treat this as read failure.
290 read_result_ = net::ERR_ABORTED;
291 MaybeCallCallbackAndDeleteThis();
292 }
293 }
294
MaybeCallCallbackAndDeleteThis()295 void MaybeCallCallbackAndDeleteThis() {
296 // |this| is deleted right after invoking |callback_|, so |callback_| should
297 // always be valid here.
298 DCHECK(callback_);
299
300 if (read_result_.has_value() && *read_result_ != net::Error::OK) {
301 // Reading from the blob failed, report that error.
302 std::move(callback_).Run(native_file_system_error::FromFileError(
303 storage::NetErrorToFileError(*read_result_)),
304 0);
305 delete this;
306 return;
307 }
308 if (!write_result_.is_null() &&
309 write_result_->status != blink::mojom::NativeFileSystemStatus::kOk) {
310 // Writing failed, report that error.
311 std::move(callback_).Run(std::move(write_result_), 0);
312 delete this;
313 return;
314 }
315 if (read_result_.has_value() && !write_result_.is_null()) {
316 // Both reading and writing succeeded, report success.
317 std::move(callback_).Run(std::move(write_result_), bytes_written_);
318 delete this;
319 return;
320 }
321 // Still waiting for the other operation to complete, so don't call the
322 // callback yet.
323 }
324
325 NativeFileSystemFileWriterImpl::WriteCallback callback_;
326 mojo::Receiver<blink::mojom::BlobReaderClient> receiver_;
327
328 base::Optional<int32_t> read_result_;
329 blink::mojom::NativeFileSystemErrorPtr write_result_;
330 uint64_t bytes_written_ = 0;
331 };
332
333 } // namespace
334
WriteImpl(uint64_t offset,mojo::PendingRemote<blink::mojom::Blob> data,WriteCallback callback)335 void NativeFileSystemFileWriterImpl::WriteImpl(
336 uint64_t offset,
337 mojo::PendingRemote<blink::mojom::Blob> data,
338 WriteCallback callback) {
339 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
340 DCHECK_EQ(GetWritePermissionStatus(),
341 blink::mojom::PermissionStatus::GRANTED);
342
343 if (is_closed()) {
344 std::move(callback).Run(
345 native_file_system_error::FromStatus(
346 NativeFileSystemStatus::kInvalidState,
347 "An attempt was made to write to a closed writer."),
348 /*bytes_written=*/0);
349 return;
350 }
351
352 MojoCreateDataPipeOptions options;
353 options.struct_size = sizeof(MojoCreateDataPipeOptions);
354 options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
355 options.element_num_bytes = 1;
356 options.capacity_num_bytes =
357 blink::BlobUtils::GetDataPipeCapacity(blink::BlobUtils::kUnknownSize);
358
359 mojo::ScopedDataPipeProducerHandle producer_handle;
360 mojo::ScopedDataPipeConsumerHandle consumer_handle;
361 MojoResult rv =
362 mojo::CreateDataPipe(&options, &producer_handle, &consumer_handle);
363 if (rv != MOJO_RESULT_OK) {
364 std::move(callback).Run(
365 native_file_system_error::FromStatus(
366 NativeFileSystemStatus::kOperationFailed,
367 "Internal read error: failed to create mojo data pipe."),
368 /*bytes_written=*/0);
369 return;
370 }
371
372 // TODO(mek): We can do this transformation from Blob to DataPipe in the
373 // renderer, and simplify the mojom exposed interface.
374 mojo::Remote<blink::mojom::Blob> blob(std::move(data));
375 mojo::PendingRemote<blink::mojom::BlobReaderClient> reader_client;
376 auto* client = new BlobReaderClient(
377 std::move(callback), reader_client.InitWithNewPipeAndPassReceiver());
378 blob->ReadAll(std::move(producer_handle), std::move(reader_client));
379 WriteStreamImpl(
380 offset, std::move(consumer_handle),
381 base::BindOnce(&BlobReaderClient::WriteCompleted, client->AsWeakPtr()));
382 }
383
WriteStreamImpl(uint64_t offset,mojo::ScopedDataPipeConsumerHandle stream,WriteStreamCallback callback)384 void NativeFileSystemFileWriterImpl::WriteStreamImpl(
385 uint64_t offset,
386 mojo::ScopedDataPipeConsumerHandle stream,
387 WriteStreamCallback callback) {
388 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
389 DCHECK_EQ(GetWritePermissionStatus(),
390 blink::mojom::PermissionStatus::GRANTED);
391
392 if (is_closed()) {
393 std::move(callback).Run(
394 native_file_system_error::FromStatus(
395 NativeFileSystemStatus::kInvalidState,
396 "An attempt was made to write to a closed writer."),
397 /*bytes_written=*/0);
398 return;
399 }
400
401 DoFileSystemOperation(
402 FROM_HERE, &FileSystemOperationRunner::WriteStream,
403 base::BindRepeating(&NativeFileSystemFileWriterImpl::DidWrite,
404 weak_factory_.GetWeakPtr(),
405 base::Owned(new WriteState{std::move(callback)})),
406 swap_url(), std::move(stream), offset);
407 }
408
DidWrite(WriteState * state,base::File::Error result,int64_t bytes,bool complete)409 void NativeFileSystemFileWriterImpl::DidWrite(WriteState* state,
410 base::File::Error result,
411 int64_t bytes,
412 bool complete) {
413 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
414
415 DCHECK(state);
416 state->bytes_written += bytes;
417 if (complete) {
418 std::move(state->callback)
419 .Run(native_file_system_error::FromFileError(result),
420 state->bytes_written);
421 }
422 }
423
TruncateImpl(uint64_t length,TruncateCallback callback)424 void NativeFileSystemFileWriterImpl::TruncateImpl(uint64_t length,
425 TruncateCallback callback) {
426 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
427 DCHECK_EQ(GetWritePermissionStatus(),
428 blink::mojom::PermissionStatus::GRANTED);
429
430 if (is_closed()) {
431 std::move(callback).Run(native_file_system_error::FromStatus(
432 NativeFileSystemStatus::kInvalidState,
433 "An attempt was made to write to a closed writer."));
434 return;
435 }
436
437 DoFileSystemOperation(
438 FROM_HERE, &FileSystemOperationRunner::Truncate,
439 base::BindOnce(
440 [](TruncateCallback callback, base::File::Error result) {
441 std::move(callback).Run(
442 native_file_system_error::FromFileError(result));
443 },
444 std::move(callback)),
445 swap_url(), length);
446 }
447
CloseImpl(CloseCallback callback)448 void NativeFileSystemFileWriterImpl::CloseImpl(CloseCallback callback) {
449 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
450 DCHECK_EQ(GetWritePermissionStatus(),
451 blink::mojom::PermissionStatus::GRANTED);
452 if (is_closed()) {
453 std::move(callback).Run(native_file_system_error::FromStatus(
454 NativeFileSystemStatus::kInvalidState,
455 "An attempt was made to close an already closed writer."));
456 return;
457 }
458
459 // Should the writer be destructed at this point, we want to allow the
460 // close operation to run its course, so we should not purge the swap file.
461 // If the after write check fails, the callback for that will clean up the
462 // swap file even if the writer was destroyed at that point.
463 state_ = State::kClosePending;
464
465 if (!RequireSecurityChecks() || !manager()->permission_context()) {
466 DidPassAfterWriteCheck(std::move(callback));
467 return;
468 }
469
470 ComputeHashForSwapFile(base::BindOnce(
471 &NativeFileSystemFileWriterImpl::DoAfterWriteCheck,
472 weak_factory_.GetWeakPtr(), base::WrapRefCounted(manager()), swap_url(),
473 std::move(callback)));
474 }
475
476 // static
DoAfterWriteCheck(base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,scoped_refptr<NativeFileSystemManagerImpl> manager,const storage::FileSystemURL & swap_url,NativeFileSystemFileWriterImpl::CloseCallback callback,base::File::Error hash_result,const std::string & hash,int64_t size)477 void NativeFileSystemFileWriterImpl::DoAfterWriteCheck(
478 base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
479 scoped_refptr<NativeFileSystemManagerImpl> manager,
480 const storage::FileSystemURL& swap_url,
481 NativeFileSystemFileWriterImpl::CloseCallback callback,
482 base::File::Error hash_result,
483 const std::string& hash,
484 int64_t size) {
485 if (!file_writer || hash_result != base::File::FILE_OK) {
486 // If writer was deleted, or calculating the hash failed try deleting the
487 // swap file and invoke the callback.
488 manager->operation_runner().PostTaskWithThisObject(
489 FROM_HERE, base::BindOnce(&RemoveSwapFile, swap_url));
490 std::move(callback).Run(native_file_system_error::FromStatus(
491 NativeFileSystemStatus::kOperationAborted,
492 "Failed to perform Safe Browsing check."));
493 return;
494 }
495
496 DCHECK_CALLED_ON_VALID_SEQUENCE(file_writer->sequence_checker_);
497
498 auto item = std::make_unique<NativeFileSystemWriteItem>();
499 item->target_file_path = file_writer->url().path();
500 item->full_path = file_writer->swap_url().path();
501 item->sha256_hash = hash;
502 item->size = size;
503 item->frame_url = file_writer->context().url;
504 item->has_user_gesture = file_writer->has_transient_user_activation_;
505 file_writer->manager()->permission_context()->PerformAfterWriteChecks(
506 std::move(item), file_writer->context().frame_id,
507 base::BindOnce(&NativeFileSystemFileWriterImpl::DidAfterWriteCheck,
508 file_writer, std::move(manager), swap_url,
509 std::move(callback)));
510 }
511
512 // static
DidAfterWriteCheck(base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,scoped_refptr<NativeFileSystemManagerImpl> manager,const storage::FileSystemURL & swap_url,NativeFileSystemFileWriterImpl::CloseCallback callback,NativeFileSystemPermissionContext::AfterWriteCheckResult result)513 void NativeFileSystemFileWriterImpl::DidAfterWriteCheck(
514 base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
515 scoped_refptr<NativeFileSystemManagerImpl> manager,
516 const storage::FileSystemURL& swap_url,
517 NativeFileSystemFileWriterImpl::CloseCallback callback,
518 NativeFileSystemPermissionContext::AfterWriteCheckResult result) {
519 if (file_writer &&
520 result ==
521 NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow) {
522 file_writer->DidPassAfterWriteCheck(std::move(callback));
523 return;
524 }
525
526 // Writer is gone, or safe browsing check failed. In this case we should
527 // try deleting the swap file and call the callback to report that close
528 // failed.
529 manager->operation_runner().PostTaskWithThisObject(
530 FROM_HERE, base::BindOnce(&RemoveSwapFile, swap_url));
531 std::move(callback).Run(native_file_system_error::FromStatus(
532 NativeFileSystemStatus::kOperationAborted,
533 "Write operation blocked by Safe Browsing."));
534 return;
535 }
536
DidPassAfterWriteCheck(CloseCallback callback)537 void NativeFileSystemFileWriterImpl::DidPassAfterWriteCheck(
538 CloseCallback callback) {
539 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
540 // If the move operation succeeds, the path pointing to the swap file
541 // will not exist anymore.
542 // In case of error, the swap file URL will point to a valid filesystem
543 // location. The file at this URL will be deleted when the mojo pipe closes.
544 base::OnceCallback<void(base::File::Error)> result_callback;
545 if (RequireSecurityChecks()) {
546 GURL referrer_url = manager()->is_off_the_record() ? GURL() : context().url;
547 mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote;
548 if (quarantine_connection_callback_) {
549 quarantine_connection_callback_.Run(
550 quarantine_remote.BindNewPipeAndPassReceiver());
551 }
552 result_callback =
553 base::BindOnce(&NativeFileSystemFileWriterImpl::DidSwapFileDoQuarantine,
554 weak_factory_.GetWeakPtr(), url(), referrer_url,
555 std::move(quarantine_remote), std::move(callback));
556 } else {
557 result_callback = base::BindOnce(
558 &NativeFileSystemFileWriterImpl::DidSwapFileSkipQuarantine,
559 weak_factory_.GetWeakPtr(), std::move(callback));
560 }
561 DoFileSystemOperation(
562 FROM_HERE, &FileSystemOperationRunner::MoveFileLocal,
563 std::move(result_callback), swap_url(), url(),
564 storage::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED);
565 }
566
DidSwapFileSkipQuarantine(CloseCallback callback,base::File::Error result)567 void NativeFileSystemFileWriterImpl::DidSwapFileSkipQuarantine(
568 CloseCallback callback,
569 base::File::Error result) {
570 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
571 if (result != base::File::FILE_OK) {
572 state_ = State::kCloseError;
573 DLOG(ERROR) << "Swap file move operation failed source: "
574 << swap_url().path() << " dest: " << url().path()
575 << " error: " << base::File::ErrorToString(result);
576 std::move(callback).Run(native_file_system_error::FromFileError(result));
577 return;
578 }
579
580 state_ = State::kClosed;
581 std::move(callback).Run(native_file_system_error::Ok());
582 }
583
584 // static
DidSwapFileDoQuarantine(base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,const storage::FileSystemURL & target_url,const GURL & referrer_url,mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,CloseCallback callback,base::File::Error result)585 void NativeFileSystemFileWriterImpl::DidSwapFileDoQuarantine(
586 base::WeakPtr<NativeFileSystemFileWriterImpl> file_writer,
587 const storage::FileSystemURL& target_url,
588 const GURL& referrer_url,
589 mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,
590 CloseCallback callback,
591 base::File::Error result) {
592 if (file_writer)
593 DCHECK_CALLED_ON_VALID_SEQUENCE(file_writer->sequence_checker_);
594
595 if (result != base::File::FILE_OK) {
596 if (file_writer)
597 file_writer->state_ = State::kCloseError;
598 DLOG(ERROR) << "Swap file move operation failed dest: " << target_url.path()
599 << " error: " << base::File::ErrorToString(result);
600 std::move(callback).Run(native_file_system_error::FromFileError(result));
601 return;
602 }
603
604 // The quarantine service operates on files identified by a base::FilePath. As
605 // such we can only quarantine files that are actual local files.
606 // On ChromeOS on the other hand anything that isn't in the sandboxed file
607 // system is also uniquely identifiable by its FileSystemURL::path(), and
608 // thus we accept all other FileSystemURL types.
609 #if defined(OS_CHROMEOS)
610 DCHECK(target_url.type() != storage::kFileSystemTypeTemporary &&
611 target_url.type() != storage::kFileSystemTypePersistent)
612 << target_url.type();
613 #else
614 DCHECK(target_url.type() == storage::kFileSystemTypeNativeLocal ||
615 target_url.type() == storage::kFileSystemTypeTest)
616 << target_url.type();
617 #endif
618
619 GURL authority_url =
620 referrer_url.is_valid() && referrer_url.SchemeIsHTTPOrHTTPS()
621 ? referrer_url
622 : GURL();
623
624 if (quarantine_remote) {
625 quarantine::mojom::Quarantine* raw_quarantine = quarantine_remote.get();
626 raw_quarantine->QuarantineFile(
627 target_url.path(), authority_url, referrer_url,
628 GetContentClient()
629 ->browser()
630 ->GetApplicationClientGUIDForQuarantineCheck(),
631 mojo::WrapCallbackWithDefaultInvokeIfNotRun(
632 base::BindOnce(&NativeFileSystemFileWriterImpl::DidAnnotateFile,
633 std::move(file_writer), std::move(callback),
634 std::move(quarantine_remote)),
635 quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED));
636 } else {
637 #if defined(OS_WIN)
638 base::ThreadPool::PostTaskAndReplyWithResult(
639 FROM_HERE, {base::MayBlock()},
640 base::BindOnce(&quarantine::SetInternetZoneIdentifierDirectly,
641 target_url.path(), authority_url, referrer_url),
642 base::BindOnce(&NativeFileSystemFileWriterImpl::DidAnnotateFile,
643 std::move(file_writer), std::move(callback),
644 std::move(quarantine_remote)));
645 #else
646 if (file_writer) {
647 file_writer->DidAnnotateFile(
648 std::move(callback), std::move(quarantine_remote),
649 quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED);
650 }
651 #endif
652 }
653 }
654
DidAnnotateFile(CloseCallback callback,mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,quarantine::mojom::QuarantineFileResult result)655 void NativeFileSystemFileWriterImpl::DidAnnotateFile(
656 CloseCallback callback,
657 mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote,
658 quarantine::mojom::QuarantineFileResult result) {
659 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
660 state_ = State::kClosed;
661
662 if (result != quarantine::mojom::QuarantineFileResult::OK &&
663 result != quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED) {
664 // If malware was detected, or the file referrer was blocked by policy, the
665 // file will be deleted at this point by AttachmentServices on Windows.
666 // There is nothing to do except to return the error message to the
667 // application.
668 std::move(callback).Run(native_file_system_error::FromStatus(
669 NativeFileSystemStatus::kOperationAborted,
670 "Write operation aborted due to security policy."));
671 return;
672 }
673
674 std::move(callback).Run(native_file_system_error::Ok());
675 }
676
ComputeHashForSwapFile(HashCallback callback)677 void NativeFileSystemFileWriterImpl::ComputeHashForSwapFile(
678 HashCallback callback) {
679 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
680
681 auto wrapped_callback = base::BindOnce(
682 [](scoped_refptr<base::SequencedTaskRunner> runner, HashCallback callback,
683 base::File::Error error, const std::string& hash, int64_t size) {
684 runner->PostTask(
685 FROM_HERE, base::BindOnce(std::move(callback), error, hash, size));
686 },
687 base::SequencedTaskRunnerHandle::Get(), std::move(callback));
688
689 manager()->operation_runner().PostTaskWithThisObject(
690 FROM_HERE, base::BindOnce(&HashCalculator::CreateAndStart,
691 base::WrapRefCounted(file_system_context()),
692 std::move(wrapped_callback), swap_url()));
693 }
694
695 base::WeakPtr<NativeFileSystemHandleBase>
AsWeakPtr()696 NativeFileSystemFileWriterImpl::AsWeakPtr() {
697 return weak_factory_.GetWeakPtr();
698 }
699
700 } // namespace content
701