|  | // 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 |