// 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(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( std::min(BytesRemaining(), static_cast(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 weak_this, int64_t result) { if (!weak_this) return; weak_this->OnIOComplete(result >= 0 ? OK : static_cast(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 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(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(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