| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/file_system_access/native_file_system_file_writer_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/services/quarantine/quarantine.h" |
| #include "content/browser/file_system_access/native_file_system_error.h" |
| #include "content/browser/file_system_access/native_file_system_manager_impl.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/common/content_client.h" |
| #include "crypto/secure_hash.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "storage/browser/file_system/file_stream_reader.h" |
| #include "storage/browser/file_system/file_system_operation_runner.h" |
| #include "third_party/blink/public/common/blob/blob_utils.h" |
| #include "third_party/blink/public/mojom/blob/blob.mojom.h" |
| #include "third_party/blink/public/mojom/file_system_access/file_system_access_error.mojom.h" |
| |
| using blink::mojom::FileSystemAccessStatus; |
| using storage::BlobDataHandle; |
| using storage::FileSystemOperation; |
| using storage::FileSystemOperationRunner; |
| |
| namespace content { |
| |
| namespace { |
| |
| // For after write checks we need the hash and size of the file. That data is |
| // calculated on the IO thread by this class. |
| // This class is ref-counted to make it easier to integrate with the |
| // FileStreamReader API where methods either return synchronously or invoke |
| // their callback asynchronously. |
| class HashCalculator : public base::RefCounted<HashCalculator> { |
| public: |
| // Must be called on the FileSystemContext's IO runner. |
| static void CreateAndStart( |
| scoped_refptr<storage::FileSystemContext> context, |
| NativeFileSystemFileWriterImpl::HashCallback callback, |
| const storage::FileSystemURL& swap_url, |
| storage::FileSystemOperationRunner*) { |
| auto calculator = base::MakeRefCounted<HashCalculator>(std::move(context), |
| std::move(callback)); |
| calculator->Start(swap_url); |
| } |
| |
| HashCalculator(scoped_refptr<storage::FileSystemContext> context, |
| NativeFileSystemFileWriterImpl::HashCallback callback) |
| : context_(std::move(context)), callback_(std::move(callback)) { |
| DCHECK(context_); |
| } |
| |
| private: |
| friend class base::RefCounted<HashCalculator>; |
| ~HashCalculator() = default; |
| |
| void Start(const storage::FileSystemURL& swap_url) { |
| reader_ = context_->CreateFileStreamReader( |
| swap_url, 0, storage::kMaximumLength, base::Time()); |
| int64_t length = |
| reader_->GetLength(base::BindOnce(&HashCalculator::GotLength, this)); |
| if (length == net::ERR_IO_PENDING) |
| return; |
| GotLength(length); |
| } |
| |
| void GotLength(int64_t length) { |
| if (length < 0) { |
| std::move(callback_).Run(storage::NetErrorToFileError(length), |
| std::string(), -1); |
| return; |
| } |
| |
| file_size_ = length; |
| ReadMore(); |
| } |
| |
| void ReadMore() { |
| DCHECK_GE(file_size_, 0); |
| int read_result = |
| reader_->Read(buffer_.get(), buffer_->size(), |
| base::BindOnce(&HashCalculator::DidRead, this)); |
| if (read_result == net::ERR_IO_PENDING) |
| return; |
| DidRead(read_result); |
| } |
| |
| void DidRead(int bytes_read) { |
| DCHECK_GE(file_size_, 0); |
| if (bytes_read < 0) { |
| std::move(callback_).Run(storage::NetErrorToFileError(bytes_read), |
| std::string(), -1); |
| return; |
| } |
| if (bytes_read == 0) { |
| std::string hash_str(hash_->GetHashLength(), 0); |
| hash_->Finish(base::data(hash_str), hash_str.size()); |
| std::move(callback_).Run(base::File::FILE_OK, hash_str, file_size_); |
| return; |
| } |
| |
| hash_->Update(buffer_->data(), bytes_read); |
| ReadMore(); |
| } |
| |
| const scoped_refptr<storage::FileSystemContext> context_; |
| NativeFileSystemFileWriterImpl::HashCallback callback_; |
| |
| const scoped_refptr<net::IOBufferWithSize> buffer_{ |
| base::MakeRefCounted<net::IOBufferWithSize>(8 * 1024)}; |
| |
| const std::unique_ptr<crypto::SecureHash> hash_{ |
| crypto::SecureHash::Create(crypto::SecureHash::SHA256)}; |
| |
| std::unique_ptr<storage::FileStreamReader> reader_; |
| int64_t file_size_ = -1; |
| }; |
| |
| void RemoveSwapFile(const storage::FileSystemURL& swap_url, |
| storage::FileSystemOperationRunner* runner) { |
| runner->Remove(swap_url, /*recursive=*/false, base::DoNothing()); |
| } |
| |
| } // namespace |
| |
| struct NativeFileSystemFileWriterImpl::WriteState { |
| WriteCallback callback; |
| uint64_t bytes_written = 0; |
| }; |
| |
| NativeFileSystemFileWriterImpl::NativeFileSystemFileWriterImpl( |
| NativeFileSystemManagerImpl* manager, |
| base::PassKey<NativeFileSystemManagerImpl> pass_key, |
| const BindingContext& context, |
| const storage::FileSystemURL& url, |
| const storage::FileSystemURL& swap_url, |
| const SharedHandleState& handle_state, |
| mojo::PendingReceiver<blink::mojom::FileSystemAccessFileWriter> receiver, |
| bool has_transient_user_activation, |
| bool auto_close, |
| download::QuarantineConnectionCallback quarantine_connection_callback) |
| : NativeFileSystemHandleBase(manager, context, url, handle_state), |
| receiver_(this, std::move(receiver)), |
| swap_url_(swap_url), |
| quarantine_connection_callback_( |
| std::move(quarantine_connection_callback)), |
| has_transient_user_activation_(has_transient_user_activation), |
| auto_close_(auto_close) { |
| DCHECK_EQ(swap_url.type(), url.type()); |
| receiver_.set_disconnect_handler(base::BindOnce( |
| &NativeFileSystemFileWriterImpl::OnDisconnect, base::Unretained(this))); |
| } |
| |
| NativeFileSystemFileWriterImpl::~NativeFileSystemFileWriterImpl() { |
| if (can_purge()) { |
| DoFileSystemOperation(FROM_HERE, &FileSystemOperationRunner::RemoveFile, |
| base::BindOnce( |
| [](const storage::FileSystemURL& swap_url, |
| base::File::Error result) { |
| if (result != base::File::FILE_OK) { |
| DLOG(ERROR) |
| << "Error Deleting Swap File, status: " |
| << base::File::ErrorToString(result) |
| << " path: " << swap_url.path(); |
| } |
| }, |
| swap_url()), |
| swap_url()); |
| } |
| } |
| |
| void NativeFileSystemFileWriterImpl::Write( |
| uint64_t offset, |
| mojo::PendingRemote<blink::mojom::Blob> data, |
| WriteCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RunWithWritePermission( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::WriteImpl, |
| weak_factory_.GetWeakPtr(), offset, std::move(data)), |
| base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result, |
| WriteCallback callback) { |
| std::move(callback).Run(std::move(result), |
| /*bytes_written=*/0); |
| }), |
| std::move(callback)); |
| } |
| |
| void NativeFileSystemFileWriterImpl::WriteStream( |
| uint64_t offset, |
| mojo::ScopedDataPipeConsumerHandle stream, |
| WriteStreamCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RunWithWritePermission( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::WriteStreamImpl, |
| weak_factory_.GetWeakPtr(), offset, std::move(stream)), |
| base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result, |
| WriteStreamCallback callback) { |
| std::move(callback).Run(std::move(result), |
| /*bytes_written=*/0); |
| }), |
| std::move(callback)); |
| } |
| |
| void NativeFileSystemFileWriterImpl::Truncate(uint64_t length, |
| TruncateCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RunWithWritePermission( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::TruncateImpl, |
| weak_factory_.GetWeakPtr(), length), |
| base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result, |
| TruncateCallback callback) { |
| std::move(callback).Run(std::move(result)); |
| }), |
| std::move(callback)); |
| } |
| |
| void NativeFileSystemFileWriterImpl::Close(CloseCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RunWithWritePermission( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::CloseImpl, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result, |
| CloseCallback callback) { |
| std::move(callback).Run(std::move(result)); |
| }), |
| std::move(callback)); |
| } |
| |
| void NativeFileSystemFileWriterImpl::Abort(AbortCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| RunWithWritePermission( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::AbortImpl, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result, |
| AbortCallback callback) { |
| std::move(callback).Run(std::move(result)); |
| }), |
| std::move(callback)); |
| } |
| |
| namespace { |
| |
| // Writing a blob to a file consists of three operations: |
| // 1) The Blob reads its data, and writes it out to a mojo data pipe producer |
| // handle. It calls BlobReaderClient::OnComplete when all data has been |
| // written. |
| // 2) WriteStream reads data from the associated mojo data pipe consumer handle |
| // as long as data is available. |
| // 3) All the read data is written to disk. Signalled by calling WriteCompleted. |
| // |
| // All of these steps are done in parallel, operating on chunks. Furthermore the |
| // OnComplete call from step 1) is done over a different mojo pipe than where |
| // the data is sent, making it possible for this to arrive either before or |
| // after step 3) completes. To make sure we report an error when any of these |
| // steps fail, this helper class waits for both the OnComplete call to arrive |
| // and for the write to finish before considering the entire write operation a |
| // success. |
| // |
| // On the other hand, as soon as we're aware of any of these steps failing, that |
| // error can be propagated, as that means the operation failed. |
| // In other words, this is like Promise.all, which resolves when all |
| // operations succeed, or rejects as soon as any operation fails. |
| // |
| // This class deletes itself after calling its callback. |
| class BlobReaderClient : public base::SupportsWeakPtr<BlobReaderClient>, |
| public blink::mojom::BlobReaderClient { |
| public: |
| BlobReaderClient( |
| NativeFileSystemFileWriterImpl::WriteCallback callback, |
| mojo::PendingReceiver<blink::mojom::BlobReaderClient> receiver) |
| : callback_(std::move(callback)), receiver_(this, std::move(receiver)) { |
| receiver_.set_disconnect_handler( |
| base::BindOnce(&BlobReaderClient::OnDisconnect, AsWeakPtr())); |
| } |
| |
| void OnCalculatedSize(uint64_t total_size, |
| uint64_t expected_content_size) override {} |
| void OnComplete(int32_t status, uint64_t data_length) override { |
| DCHECK(!read_result_.has_value()); |
| read_result_ = status; |
| MaybeCallCallbackAndDeleteThis(); |
| } |
| |
| void WriteCompleted(blink::mojom::FileSystemAccessErrorPtr result, |
| uint64_t bytes_written) { |
| DCHECK(!write_result_); |
| write_result_ = std::move(result); |
| bytes_written_ = bytes_written; |
| MaybeCallCallbackAndDeleteThis(); |
| } |
| |
| private: |
| friend class base::RefCounted<BlobReaderClient>; |
| ~BlobReaderClient() override = default; |
| |
| void OnDisconnect() { |
| if (!read_result_.has_value()) { |
| // Disconnected without getting a read result, treat this as read failure. |
| read_result_ = net::ERR_ABORTED; |
| MaybeCallCallbackAndDeleteThis(); |
| } |
| } |
| |
| void MaybeCallCallbackAndDeleteThis() { |
| // |this| is deleted right after invoking |callback_|, so |callback_| should |
| // always be valid here. |
| DCHECK(callback_); |
| |
| if (read_result_.has_value() && *read_result_ != net::Error::OK) { |
| // Reading from the blob failed, report that error. |
| std::move(callback_).Run(native_file_system_error::FromFileError( |
| storage::NetErrorToFileError(*read_result_)), |
| 0); |
| delete this; |
| return; |
| } |
| if (!write_result_.is_null() && |
| write_result_->status != blink::mojom::FileSystemAccessStatus::kOk) { |
| // Writing failed, report that error. |
| std::move(callback_).Run(std::move(write_result_), 0); |
| delete this; |
| return; |
| } |
| if (read_result_.has_value() && !write_result_.is_null()) { |
| // Both reading and writing succeeded, report success. |
| std::move(callback_).Run(std::move(write_result_), bytes_written_); |
| delete this; |
| return; |
| } |
| // Still waiting for the other operation to complete, so don't call the |
| // callback yet. |
| } |
| |
| NativeFileSystemFileWriterImpl::WriteCallback callback_; |
| mojo::Receiver<blink::mojom::BlobReaderClient> receiver_; |
| |
| base::Optional<int32_t> read_result_; |
| blink::mojom::FileSystemAccessErrorPtr write_result_; |
| uint64_t bytes_written_ = 0; |
| }; |
| |
| } // namespace |
| |
| // Do not call this method if |close_callback_| is not set. |
| void NativeFileSystemFileWriterImpl::CallCloseCallbackAndMaybeDeleteThis( |
| blink::mojom::FileSystemAccessErrorPtr result) { |
| std::move(close_callback_).Run(std::move(result)); |
| |
| if (!receiver_.is_bound()) { |
| // |this| is deleted after this call. |
| manager()->RemoveFileWriter(this); |
| } |
| } |
| |
| void NativeFileSystemFileWriterImpl::OnDisconnect() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| receiver_.reset(); |
| |
| if (!close_callback_) { |
| if (!is_closed() && auto_close_) { |
| // Close the Writer. |this| is deleted via |
| // CallCloseCallbackAndMaybeDeleteThis when Close finishes. |
| Close(base::BindOnce([](blink::mojom::FileSystemAccessErrorPtr result) { |
| if (result->status != blink::mojom::FileSystemAccessStatus::kOk) { |
| DLOG(ERROR) << "AutoClose failed with result:" |
| << base::File::ErrorToString(result->file_error); |
| } |
| })); |
| return; |
| } |
| // |this| is deleted after this call. |
| manager()->RemoveFileWriter(this); |
| } |
| } |
| |
| void NativeFileSystemFileWriterImpl::WriteImpl( |
| uint64_t offset, |
| mojo::PendingRemote<blink::mojom::Blob> data, |
| WriteCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(GetWritePermissionStatus(), |
| blink::mojom::PermissionStatus::GRANTED); |
| |
| if (is_closed()) { |
| std::move(callback).Run( |
| native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kInvalidState, |
| "An attempt was made to write to a closed writer."), |
| /*bytes_written=*/0); |
| return; |
| } |
| |
| MojoCreateDataPipeOptions options; |
| options.struct_size = sizeof(MojoCreateDataPipeOptions); |
| options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE; |
| options.element_num_bytes = 1; |
| options.capacity_num_bytes = |
| blink::BlobUtils::GetDataPipeCapacity(blink::BlobUtils::kUnknownSize); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| MojoResult rv = |
| mojo::CreateDataPipe(&options, &producer_handle, &consumer_handle); |
| if (rv != MOJO_RESULT_OK) { |
| std::move(callback).Run( |
| native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kOperationFailed, |
| "Internal read error: failed to create mojo data pipe."), |
| /*bytes_written=*/0); |
| return; |
| } |
| |
| // TODO(mek): We can do this transformation from Blob to DataPipe in the |
| // renderer, and simplify the mojom exposed interface. |
| mojo::Remote<blink::mojom::Blob> blob(std::move(data)); |
| mojo::PendingRemote<blink::mojom::BlobReaderClient> reader_client; |
| auto* client = new BlobReaderClient( |
| std::move(callback), reader_client.InitWithNewPipeAndPassReceiver()); |
| blob->ReadAll(std::move(producer_handle), std::move(reader_client)); |
| WriteStreamImpl( |
| offset, std::move(consumer_handle), |
| base::BindOnce(&BlobReaderClient::WriteCompleted, client->AsWeakPtr())); |
| } |
| |
| void NativeFileSystemFileWriterImpl::WriteStreamImpl( |
| uint64_t offset, |
| mojo::ScopedDataPipeConsumerHandle stream, |
| WriteStreamCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(GetWritePermissionStatus(), |
| blink::mojom::PermissionStatus::GRANTED); |
| |
| if (is_closed()) { |
| std::move(callback).Run( |
| native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kInvalidState, |
| "An attempt was made to write to a closed writer."), |
| /*bytes_written=*/0); |
| return; |
| } |
| |
| DoFileSystemOperation( |
| FROM_HERE, &FileSystemOperationRunner::WriteStream, |
| base::BindRepeating(&NativeFileSystemFileWriterImpl::DidWrite, |
| weak_factory_.GetWeakPtr(), |
| base::Owned(new WriteState{std::move(callback)})), |
| swap_url(), std::move(stream), offset); |
| } |
| |
| void NativeFileSystemFileWriterImpl::DidWrite(WriteState* state, |
| base::File::Error result, |
| int64_t bytes, |
| bool complete) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DCHECK(state); |
| state->bytes_written += bytes; |
| if (complete) { |
| std::move(state->callback) |
| .Run(native_file_system_error::FromFileError(result), |
| state->bytes_written); |
| } |
| } |
| |
| void NativeFileSystemFileWriterImpl::TruncateImpl(uint64_t length, |
| TruncateCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(GetWritePermissionStatus(), |
| blink::mojom::PermissionStatus::GRANTED); |
| |
| if (is_closed()) { |
| std::move(callback).Run(native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kInvalidState, |
| "An attempt was made to write to a closed writer.")); |
| return; |
| } |
| |
| DoFileSystemOperation( |
| FROM_HERE, &FileSystemOperationRunner::Truncate, |
| base::BindOnce( |
| [](TruncateCallback callback, base::File::Error result) { |
| std::move(callback).Run( |
| native_file_system_error::FromFileError(result)); |
| }, |
| std::move(callback)), |
| swap_url(), length); |
| } |
| |
| void NativeFileSystemFileWriterImpl::CloseImpl(CloseCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_EQ(GetWritePermissionStatus(), |
| blink::mojom::PermissionStatus::GRANTED); |
| if (is_closed()) { |
| std::move(callback).Run(native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kInvalidState, |
| "An attempt was made to close an already closed writer.")); |
| return; |
| } |
| |
| close_callback_ = std::move(callback); |
| state_ = State::kClosePending; |
| |
| if (!RequireSecurityChecks() || !manager()->permission_context()) { |
| DidAfterWriteCheck( |
| NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow); |
| return; |
| } |
| |
| ComputeHashForSwapFile( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::DoAfterWriteCheck, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void NativeFileSystemFileWriterImpl::AbortImpl(AbortCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (is_closed()) { |
| std::move(callback).Run(native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kInvalidState, |
| "An attempt was made to abort an already closed writer.")); |
| return; |
| } |
| |
| state_ = State::kClosed; |
| auto_close_ = false; |
| |
| std::move(callback).Run(native_file_system_error::Ok()); |
| } |
| |
| // static |
| void NativeFileSystemFileWriterImpl::DoAfterWriteCheck( |
| base::File::Error hash_result, |
| const std::string& hash, |
| int64_t size) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (hash_result != base::File::FILE_OK) { |
| // Calculating the hash failed try deleting the swap file and invoke the |
| // callback. |
| manager()->operation_runner().PostTaskWithThisObject( |
| FROM_HERE, base::BindOnce(&RemoveSwapFile, swap_url())); |
| CallCloseCallbackAndMaybeDeleteThis(native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kOperationAborted, |
| "Failed to perform Safe Browsing check.")); |
| return; |
| } |
| |
| auto item = std::make_unique<NativeFileSystemWriteItem>(); |
| item->target_file_path = url().path(); |
| item->full_path = swap_url().path(); |
| item->sha256_hash = hash; |
| item->size = size; |
| item->frame_url = context().url; |
| item->has_user_gesture = has_transient_user_activation_; |
| manager()->permission_context()->PerformAfterWriteChecks( |
| std::move(item), context().frame_id, |
| base::BindOnce(&NativeFileSystemFileWriterImpl::DidAfterWriteCheck, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void NativeFileSystemFileWriterImpl::DidAfterWriteCheck( |
| NativeFileSystemPermissionContext::AfterWriteCheckResult result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (result != |
| NativeFileSystemPermissionContext::AfterWriteCheckResult::kAllow) { |
| // Safe browsing check failed. In this case we should try deleting the swap |
| // file and call the callback to report that close failed. |
| manager()->operation_runner().PostTaskWithThisObject( |
| FROM_HERE, base::BindOnce(&RemoveSwapFile, swap_url())); |
| CallCloseCallbackAndMaybeDeleteThis(native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kOperationAborted, |
| "Write operation blocked by Safe Browsing.")); |
| return; |
| } |
| |
| // If the move operation succeeds, the path pointing to the swap file |
| // will not exist anymore. |
| // In case of error, the swap file URL will point to a valid filesystem |
| // location. The file at this URL will be deleted when the mojo pipe closes. |
| base::OnceCallback<void(base::File::Error)> result_callback; |
| if (RequireSecurityChecks()) { |
| GURL referrer_url = manager()->is_off_the_record() ? GURL() : context().url; |
| mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote; |
| if (quarantine_connection_callback_) { |
| quarantine_connection_callback_.Run( |
| quarantine_remote.BindNewPipeAndPassReceiver()); |
| } |
| result_callback = |
| base::BindOnce(&NativeFileSystemFileWriterImpl::DidSwapFileDoQuarantine, |
| weak_factory_.GetWeakPtr(), url(), referrer_url, |
| std::move(quarantine_remote)); |
| } else { |
| result_callback = base::BindOnce( |
| &NativeFileSystemFileWriterImpl::DidSwapFileSkipQuarantine, |
| weak_factory_.GetWeakPtr()); |
| } |
| DoFileSystemOperation( |
| FROM_HERE, &FileSystemOperationRunner::MoveFileLocal, |
| std::move(result_callback), swap_url(), url(), |
| storage::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED); |
| } |
| |
| void NativeFileSystemFileWriterImpl::DidSwapFileSkipQuarantine( |
| base::File::Error result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (result != base::File::FILE_OK) { |
| state_ = State::kCloseError; |
| DLOG(ERROR) << "Swap file move operation failed source: " |
| << swap_url().path() << " dest: " << url().path() |
| << " error: " << base::File::ErrorToString(result); |
| CallCloseCallbackAndMaybeDeleteThis( |
| native_file_system_error::FromFileError(result)); |
| return; |
| } |
| |
| state_ = State::kClosed; |
| CallCloseCallbackAndMaybeDeleteThis(native_file_system_error::Ok()); |
| } |
| |
| void NativeFileSystemFileWriterImpl::DidSwapFileDoQuarantine( |
| const storage::FileSystemURL& target_url, |
| const GURL& referrer_url, |
| mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote, |
| base::File::Error result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (result != base::File::FILE_OK) { |
| state_ = State::kCloseError; |
| DLOG(ERROR) << "Swap file move operation failed dest: " << target_url.path() |
| << " error: " << base::File::ErrorToString(result); |
| CallCloseCallbackAndMaybeDeleteThis( |
| native_file_system_error::FromFileError(result)); |
| return; |
| } |
| |
| // The quarantine service operates on files identified by a base::FilePath. As |
| // such we can only quarantine files that are actual local files. |
| // On ChromeOS on the other hand anything that isn't in the sandboxed file |
| // system is also uniquely identifiable by its FileSystemURL::path(), and |
| // thus we accept all other FileSystemURL types. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| DCHECK(target_url.type() != storage::kFileSystemTypeTemporary && |
| target_url.type() != storage::kFileSystemTypePersistent) |
| << target_url.type(); |
| #else |
| DCHECK(target_url.type() == storage::kFileSystemTypeNativeLocal || |
| target_url.type() == storage::kFileSystemTypeTest) |
| << target_url.type(); |
| #endif |
| |
| GURL authority_url = |
| referrer_url.is_valid() && referrer_url.SchemeIsHTTPOrHTTPS() |
| ? referrer_url |
| : GURL(); |
| |
| if (quarantine_remote) { |
| quarantine::mojom::Quarantine* raw_quarantine = quarantine_remote.get(); |
| raw_quarantine->QuarantineFile( |
| target_url.path(), authority_url, referrer_url, |
| GetContentClient() |
| ->browser() |
| ->GetApplicationClientGUIDForQuarantineCheck(), |
| mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| base::BindOnce(&NativeFileSystemFileWriterImpl::DidAnnotateFile, |
| weak_factory_.GetWeakPtr(), |
| std::move(quarantine_remote)), |
| quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED)); |
| } else { |
| #if defined(OS_WIN) |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&quarantine::SetInternetZoneIdentifierDirectly, |
| target_url.path(), authority_url, referrer_url), |
| base::BindOnce(&NativeFileSystemFileWriterImpl::DidAnnotateFile, |
| weak_factory_.GetWeakPtr(), |
| std::move(quarantine_remote))); |
| #else |
| DidAnnotateFile(std::move(quarantine_remote), |
| quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED); |
| #endif |
| } |
| } |
| |
| void NativeFileSystemFileWriterImpl::DidAnnotateFile( |
| mojo::Remote<quarantine::mojom::Quarantine> quarantine_remote, |
| quarantine::mojom::QuarantineFileResult result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| state_ = State::kClosed; |
| |
| if (result != quarantine::mojom::QuarantineFileResult::OK && |
| result != quarantine::mojom::QuarantineFileResult::ANNOTATION_FAILED) { |
| // If malware was detected, or the file referrer was blocked by policy, the |
| // file will be deleted at this point by AttachmentServices on Windows. |
| // There is nothing to do except to return the error message to the |
| // application. |
| CallCloseCallbackAndMaybeDeleteThis(native_file_system_error::FromStatus( |
| FileSystemAccessStatus::kOperationAborted, |
| "Write operation aborted due to security policy.")); |
| return; |
| } |
| |
| CallCloseCallbackAndMaybeDeleteThis(native_file_system_error::Ok()); |
| } |
| |
| void NativeFileSystemFileWriterImpl::ComputeHashForSwapFile( |
| HashCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto wrapped_callback = base::BindOnce( |
| [](scoped_refptr<base::SequencedTaskRunner> runner, HashCallback callback, |
| base::File::Error error, const std::string& hash, int64_t size) { |
| runner->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), error, hash, size)); |
| }, |
| base::SequencedTaskRunnerHandle::Get(), std::move(callback)); |
| |
| manager()->operation_runner().PostTaskWithThisObject( |
| FROM_HERE, base::BindOnce(&HashCalculator::CreateAndStart, |
| base::WrapRefCounted(file_system_context()), |
| std::move(wrapped_callback), swap_url())); |
| } |
| |
| base::WeakPtr<NativeFileSystemHandleBase> |
| NativeFileSystemFileWriterImpl::AsWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace content |