| // Copyright (c) 2012 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 "net/base/upload_file_element_reader.h" |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/location.h" |
| #include "base/task_runner.h" |
| #include "base/task_runner_util.h" |
| #include "net/base/file_stream.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // In tests, this value is used to override the return value of |
| // UploadFileElementReader::GetContentLength() when set to non-zero. |
| uint64_t overriding_content_length = 0; |
| |
| } // namespace |
| |
| UploadFileElementReader::UploadFileElementReader( |
| base::TaskRunner* task_runner, |
| base::File file, |
| const base::FilePath& path, |
| uint64_t range_offset, |
| uint64_t range_length, |
| const base::Time& expected_modification_time) |
| : task_runner_(task_runner), |
| path_(path), |
| range_offset_(range_offset), |
| range_length_(range_length), |
| expected_modification_time_(expected_modification_time), |
| content_length_(0), |
| bytes_remaining_(0), |
| next_state_(State::IDLE), |
| init_called_while_operation_pending_(false), |
| weak_ptr_factory_(this) { |
| DCHECK(file.IsValid()); |
| DCHECK(task_runner_.get()); |
| file_stream_ = std::make_unique<FileStream>(std::move(file), task_runner); |
| } |
| |
| UploadFileElementReader::UploadFileElementReader( |
| base::TaskRunner* task_runner, |
| const base::FilePath& path, |
| uint64_t range_offset, |
| uint64_t range_length, |
| const base::Time& expected_modification_time) |
| : task_runner_(task_runner), |
| path_(path), |
| range_offset_(range_offset), |
| range_length_(range_length), |
| expected_modification_time_(expected_modification_time), |
| content_length_(0), |
| bytes_remaining_(0), |
| next_state_(State::IDLE), |
| init_called_while_operation_pending_(false), |
| weak_ptr_factory_(this) { |
| DCHECK(task_runner_.get()); |
| } |
| |
| UploadFileElementReader::~UploadFileElementReader() = default; |
| |
| const UploadFileElementReader* UploadFileElementReader::AsFileReader() const { |
| return this; |
| } |
| |
| int UploadFileElementReader::Init(CompletionOnceCallback callback) { |
| DCHECK(!callback.is_null()); |
| |
| bytes_remaining_ = 0; |
| content_length_ = 0; |
| pending_callback_.Reset(); |
| |
| // If the file is being opened, just update the callback, and continue |
| // waiting. |
| if (next_state_ == State::OPEN_COMPLETE) { |
| DCHECK(file_stream_); |
| pending_callback_ = std::move(callback); |
| return ERR_IO_PENDING; |
| } |
| |
| // If there's already a pending operation, wait for it to complete before |
| // restarting the request. |
| if (next_state_ != State::IDLE) { |
| init_called_while_operation_pending_ = true; |
| pending_callback_ = std::move(callback); |
| return ERR_IO_PENDING; |
| } |
| |
| DCHECK(!init_called_while_operation_pending_); |
| |
| if (file_stream_) { |
| // If the file is already open, just re-use it. |
| // TODO(mmenke): Consider reusing file info, too. |
| next_state_ = State::SEEK; |
| } else { |
| next_state_ = State::OPEN; |
| } |
| int result = DoLoop(OK); |
| if (result == ERR_IO_PENDING) |
| pending_callback_ = std::move(callback); |
| return result; |
| } |
| |
| uint64_t UploadFileElementReader::GetContentLength() const { |
| if (overriding_content_length) |
| return overriding_content_length; |
| return content_length_; |
| } |
| |
| uint64_t UploadFileElementReader::BytesRemaining() const { |
| return bytes_remaining_; |
| } |
| |
| int UploadFileElementReader::Read(IOBuffer* buf, |
| int buf_length, |
| CompletionOnceCallback callback) { |
| DCHECK(!callback.is_null()); |
| DCHECK_EQ(next_state_, State::IDLE); |
| DCHECK(file_stream_); |
| |
| int num_bytes_to_read = static_cast<int>( |
| std::min(BytesRemaining(), static_cast<uint64_t>(buf_length))); |
| if (num_bytes_to_read == 0) |
| return 0; |
| |
| next_state_ = State::READ_COMPLETE; |
| int result = file_stream_->Read( |
| buf, num_bytes_to_read, |
| base::BindOnce(base::IgnoreResult(&UploadFileElementReader::OnIOComplete), |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| if (result != ERR_IO_PENDING) |
| result = DoLoop(result); |
| |
| if (result == ERR_IO_PENDING) |
| pending_callback_ = std::move(callback); |
| |
| return result; |
| } |
| |
| int UploadFileElementReader::DoLoop(int result) { |
| DCHECK_NE(result, ERR_IO_PENDING); |
| |
| if (init_called_while_operation_pending_) { |
| // File should already have been opened successfully. |
| DCHECK_NE(next_state_, State::OPEN_COMPLETE); |
| |
| next_state_ = State::SEEK; |
| init_called_while_operation_pending_ = false; |
| result = net::OK; |
| } |
| |
| while (next_state_ != State::IDLE && result != ERR_IO_PENDING) { |
| State state = next_state_; |
| next_state_ = State::IDLE; |
| switch (state) { |
| case State::IDLE: |
| NOTREACHED(); |
| break; |
| case State::OPEN: |
| // Ignore previous result here. It's typically OK, but if Init() |
| // interrupted the previous operation, it may be an error. |
| result = DoOpen(); |
| break; |
| case State::OPEN_COMPLETE: |
| result = DoOpenComplete(result); |
| break; |
| case State::SEEK: |
| DCHECK_EQ(OK, result); |
| result = DoSeek(); |
| break; |
| case State::GET_FILE_INFO: |
| result = DoGetFileInfo(result); |
| break; |
| case State::GET_FILE_INFO_COMPLETE: |
| result = DoGetFileInfoComplete(result); |
| break; |
| |
| case State::READ_COMPLETE: |
| result = DoReadComplete(result); |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| int UploadFileElementReader::DoOpen() { |
| DCHECK(!file_stream_); |
| |
| next_state_ = State::OPEN_COMPLETE; |
| |
| file_stream_.reset(new FileStream(task_runner_.get())); |
| int result = file_stream_->Open( |
| path_, |
| base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC, |
| base::BindOnce(&UploadFileElementReader::OnIOComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| DCHECK_GT(0, result); |
| return result; |
| } |
| |
| int UploadFileElementReader::DoOpenComplete(int result) { |
| if (result < 0) { |
| DLOG(WARNING) << "Failed to open \"" << path_.value() |
| << "\" for reading: " << result; |
| file_stream_.reset(); |
| return result; |
| } |
| |
| if (range_offset_) { |
| next_state_ = State::SEEK; |
| } else { |
| next_state_ = State::GET_FILE_INFO; |
| } |
| return net::OK; |
| } |
| |
| int UploadFileElementReader::DoSeek() { |
| next_state_ = State::GET_FILE_INFO; |
| return file_stream_->Seek( |
| range_offset_, |
| base::BindOnce( |
| [](base::WeakPtr<UploadFileElementReader> weak_this, int64_t result) { |
| if (!weak_this) |
| return; |
| weak_this->OnIOComplete(result >= 0 ? OK |
| : static_cast<int>(result)); |
| }, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| int UploadFileElementReader::DoGetFileInfo(int result) { |
| if (result < 0) { |
| DLOG(WARNING) << "Failed to seek \"" << path_.value() |
| << "\" to offset: " << range_offset_ << " (" << result << ")"; |
| return result; |
| } |
| |
| next_state_ = State::GET_FILE_INFO_COMPLETE; |
| |
| base::File::Info* owned_file_info = new base::File::Info; |
| result = file_stream_->GetFileInfo( |
| owned_file_info, |
| base::BindOnce( |
| [](base::WeakPtr<UploadFileElementReader> weak_this, |
| base::File::Info* file_info, int result) { |
| if (!weak_this) |
| return; |
| weak_this->file_info_ = *file_info; |
| weak_this->OnIOComplete(result); |
| }, |
| weak_ptr_factory_.GetWeakPtr(), base::Owned(owned_file_info))); |
| // GetFileInfo() can't succeed synchronously. |
| DCHECK_NE(result, OK); |
| return result; |
| } |
| |
| int UploadFileElementReader::DoGetFileInfoComplete(int result) { |
| if (result != OK) { |
| DLOG(WARNING) << "Failed to get file info of \"" << path_.value() << "\""; |
| return result; |
| } |
| |
| int64_t length = file_info_.size; |
| if (range_offset_ < static_cast<uint64_t>(length)) { |
| // Compensate for the offset. |
| length = std::min(length - range_offset_, range_length_); |
| } |
| |
| // If the underlying file has been changed and the expected file modification |
| // time is set, treat it as error. Note that |expected_modification_time_| may |
| // have gone through multiple conversion steps involving loss of precision |
| // (including conversion to time_t). Therefore the check below only verifies |
| // that the timestamps are within one second of each other. This check is used |
| // for sliced files. |
| if (!expected_modification_time_.is_null() && |
| (expected_modification_time_ - file_info_.last_modified) |
| .magnitude() |
| .InSeconds() != 0) { |
| return ERR_UPLOAD_FILE_CHANGED; |
| } |
| |
| content_length_ = length; |
| bytes_remaining_ = GetContentLength(); |
| return result; |
| } |
| |
| int UploadFileElementReader::DoReadComplete(int result) { |
| if (result == 0) // Reached end-of-file earlier than expected. |
| return ERR_UPLOAD_FILE_CHANGED; |
| |
| if (result > 0) { |
| DCHECK_GE(bytes_remaining_, static_cast<uint64_t>(result)); |
| bytes_remaining_ -= result; |
| } |
| |
| return result; |
| } |
| |
| void UploadFileElementReader::OnIOComplete(int result) { |
| DCHECK(pending_callback_); |
| |
| result = DoLoop(result); |
| |
| if (result != ERR_IO_PENDING) |
| std::move(pending_callback_).Run(result); |
| } |
| |
| UploadFileElementReader::ScopedOverridingContentLengthForTests:: |
| ScopedOverridingContentLengthForTests(uint64_t value) { |
| overriding_content_length = value; |
| } |
| |
| UploadFileElementReader::ScopedOverridingContentLengthForTests:: |
| ~ScopedOverridingContentLengthForTests() { |
| overriding_content_length = 0; |
| } |
| |
| } // namespace net |