1 // Copyright 2012 The Chromium Authors
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 "net/base/upload_file_element_reader.h"
6
7 #include <memory>
8
9 #include "base/files/file_util.h"
10 #include "base/functional/bind.h"
11 #include "base/location.h"
12 #include "base/task/task_runner.h"
13 #include "net/base/file_stream.h"
14 #include "net/base/io_buffer.h"
15 #include "net/base/net_errors.h"
16
17 namespace net {
18
19 namespace {
20
21 // In tests, this value is used to override the return value of
22 // UploadFileElementReader::GetContentLength() when set to non-zero.
23 uint64_t overriding_content_length = 0;
24
25 } // namespace
26
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)27 UploadFileElementReader::UploadFileElementReader(
28 base::TaskRunner* task_runner,
29 base::File file,
30 const base::FilePath& path,
31 uint64_t range_offset,
32 uint64_t range_length,
33 const base::Time& expected_modification_time)
34 : task_runner_(task_runner),
35 path_(path),
36 range_offset_(range_offset),
37 range_length_(range_length),
38 expected_modification_time_(expected_modification_time) {
39 DCHECK(file.IsValid());
40 DCHECK(task_runner_.get());
41 file_stream_ = std::make_unique<FileStream>(std::move(file), task_runner);
42 }
43
UploadFileElementReader(base::TaskRunner * task_runner,const base::FilePath & path,uint64_t range_offset,uint64_t range_length,const base::Time & expected_modification_time)44 UploadFileElementReader::UploadFileElementReader(
45 base::TaskRunner* task_runner,
46 const base::FilePath& path,
47 uint64_t range_offset,
48 uint64_t range_length,
49 const base::Time& expected_modification_time)
50 : task_runner_(task_runner),
51 path_(path),
52 range_offset_(range_offset),
53 range_length_(range_length),
54 expected_modification_time_(expected_modification_time) {
55 DCHECK(task_runner_.get());
56 }
57
58 UploadFileElementReader::~UploadFileElementReader() = default;
59
AsFileReader() const60 const UploadFileElementReader* UploadFileElementReader::AsFileReader() const {
61 return this;
62 }
63
Init(CompletionOnceCallback callback)64 int UploadFileElementReader::Init(CompletionOnceCallback callback) {
65 DCHECK(!callback.is_null());
66
67 bytes_remaining_ = 0;
68 content_length_ = 0;
69 pending_callback_.Reset();
70
71 // If the file is being opened, just update the callback, and continue
72 // waiting.
73 if (next_state_ == State::OPEN_COMPLETE) {
74 DCHECK(file_stream_);
75 pending_callback_ = std::move(callback);
76 return ERR_IO_PENDING;
77 }
78
79 // If there's already a pending operation, wait for it to complete before
80 // restarting the request.
81 if (next_state_ != State::IDLE) {
82 init_called_while_operation_pending_ = true;
83 pending_callback_ = std::move(callback);
84 return ERR_IO_PENDING;
85 }
86
87 DCHECK(!init_called_while_operation_pending_);
88
89 if (file_stream_) {
90 // If the file is already open, just re-use it.
91 // TODO(mmenke): Consider reusing file info, too.
92 next_state_ = State::SEEK;
93 } else {
94 next_state_ = State::OPEN;
95 }
96 int result = DoLoop(OK);
97 if (result == ERR_IO_PENDING)
98 pending_callback_ = std::move(callback);
99 return result;
100 }
101
GetContentLength() const102 uint64_t UploadFileElementReader::GetContentLength() const {
103 if (overriding_content_length)
104 return overriding_content_length;
105 return content_length_;
106 }
107
BytesRemaining() const108 uint64_t UploadFileElementReader::BytesRemaining() const {
109 return bytes_remaining_;
110 }
111
Read(IOBuffer * buf,int buf_length,CompletionOnceCallback callback)112 int UploadFileElementReader::Read(IOBuffer* buf,
113 int buf_length,
114 CompletionOnceCallback callback) {
115 DCHECK(!callback.is_null());
116 DCHECK_EQ(next_state_, State::IDLE);
117 DCHECK(file_stream_);
118
119 int num_bytes_to_read = static_cast<int>(
120 std::min(BytesRemaining(), static_cast<uint64_t>(buf_length)));
121 if (num_bytes_to_read == 0)
122 return 0;
123
124 next_state_ = State::READ_COMPLETE;
125 int result = file_stream_->Read(
126 buf, num_bytes_to_read,
127 base::BindOnce(base::IgnoreResult(&UploadFileElementReader::OnIOComplete),
128 weak_ptr_factory_.GetWeakPtr()));
129
130 if (result != ERR_IO_PENDING)
131 result = DoLoop(result);
132
133 if (result == ERR_IO_PENDING)
134 pending_callback_ = std::move(callback);
135
136 return result;
137 }
138
DoLoop(int result)139 int UploadFileElementReader::DoLoop(int result) {
140 DCHECK_NE(result, ERR_IO_PENDING);
141
142 if (init_called_while_operation_pending_) {
143 // File should already have been opened successfully.
144 DCHECK_NE(next_state_, State::OPEN_COMPLETE);
145
146 next_state_ = State::SEEK;
147 init_called_while_operation_pending_ = false;
148 result = net::OK;
149 }
150
151 while (next_state_ != State::IDLE && result != ERR_IO_PENDING) {
152 State state = next_state_;
153 next_state_ = State::IDLE;
154 switch (state) {
155 case State::IDLE:
156 NOTREACHED();
157 break;
158 case State::OPEN:
159 // Ignore previous result here. It's typically OK, but if Init()
160 // interrupted the previous operation, it may be an error.
161 result = DoOpen();
162 break;
163 case State::OPEN_COMPLETE:
164 result = DoOpenComplete(result);
165 break;
166 case State::SEEK:
167 DCHECK_EQ(OK, result);
168 result = DoSeek();
169 break;
170 case State::GET_FILE_INFO:
171 result = DoGetFileInfo(result);
172 break;
173 case State::GET_FILE_INFO_COMPLETE:
174 result = DoGetFileInfoComplete(result);
175 break;
176
177 case State::READ_COMPLETE:
178 result = DoReadComplete(result);
179 break;
180 }
181 }
182
183 return result;
184 }
185
DoOpen()186 int UploadFileElementReader::DoOpen() {
187 DCHECK(!file_stream_);
188
189 next_state_ = State::OPEN_COMPLETE;
190
191 file_stream_ = std::make_unique<FileStream>(task_runner_.get());
192 int result = file_stream_->Open(
193 path_,
194 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC,
195 base::BindOnce(&UploadFileElementReader::OnIOComplete,
196 weak_ptr_factory_.GetWeakPtr()));
197 DCHECK_GT(0, result);
198 return result;
199 }
200
DoOpenComplete(int result)201 int UploadFileElementReader::DoOpenComplete(int result) {
202 if (result < 0) {
203 DLOG(WARNING) << "Failed to open \"" << path_.value()
204 << "\" for reading: " << result;
205 file_stream_.reset();
206 return result;
207 }
208
209 if (range_offset_) {
210 next_state_ = State::SEEK;
211 } else {
212 next_state_ = State::GET_FILE_INFO;
213 }
214 return net::OK;
215 }
216
DoSeek()217 int UploadFileElementReader::DoSeek() {
218 next_state_ = State::GET_FILE_INFO;
219 return file_stream_->Seek(
220 range_offset_,
221 base::BindOnce(
222 [](base::WeakPtr<UploadFileElementReader> weak_this, int64_t result) {
223 if (!weak_this)
224 return;
225 weak_this->OnIOComplete(result >= 0 ? OK
226 : static_cast<int>(result));
227 },
228 weak_ptr_factory_.GetWeakPtr()));
229 }
230
DoGetFileInfo(int result)231 int UploadFileElementReader::DoGetFileInfo(int result) {
232 if (result < 0) {
233 DLOG(WARNING) << "Failed to seek \"" << path_.value()
234 << "\" to offset: " << range_offset_ << " (" << result << ")";
235 return result;
236 }
237
238 next_state_ = State::GET_FILE_INFO_COMPLETE;
239
240 auto file_info = std::make_unique<base::File::Info>();
241 auto* file_info_ptr = file_info.get();
242 result = file_stream_->GetFileInfo(
243 file_info_ptr,
244 base::BindOnce(
245 [](base::WeakPtr<UploadFileElementReader> weak_this,
246 std::unique_ptr<base::File::Info> file_info, int result) {
247 if (!weak_this)
248 return;
249 weak_this->file_info_ = *file_info;
250 weak_this->OnIOComplete(result);
251 },
252 weak_ptr_factory_.GetWeakPtr(), std::move(file_info)));
253 // GetFileInfo() can't succeed synchronously.
254 DCHECK_NE(result, OK);
255 return result;
256 }
257
DoGetFileInfoComplete(int result)258 int UploadFileElementReader::DoGetFileInfoComplete(int result) {
259 if (result != OK) {
260 DLOG(WARNING) << "Failed to get file info of \"" << path_.value() << "\"";
261 return result;
262 }
263
264 int64_t length = file_info_.size;
265 if (range_offset_ < static_cast<uint64_t>(length)) {
266 // Compensate for the offset.
267 length = std::min(length - range_offset_, range_length_);
268 }
269
270 // If the underlying file has been changed and the expected file modification
271 // time is set, treat it as error. Note that |expected_modification_time_| may
272 // have gone through multiple conversion steps involving loss of precision
273 // (including conversion to time_t). Therefore the check below only verifies
274 // that the timestamps are within one second of each other. This check is used
275 // for sliced files.
276 if (!expected_modification_time_.is_null() &&
277 (expected_modification_time_ - file_info_.last_modified)
278 .magnitude()
279 .InSeconds() != 0) {
280 return ERR_UPLOAD_FILE_CHANGED;
281 }
282
283 content_length_ = length;
284 bytes_remaining_ = GetContentLength();
285 return result;
286 }
287
DoReadComplete(int result)288 int UploadFileElementReader::DoReadComplete(int result) {
289 if (result == 0) // Reached end-of-file earlier than expected.
290 return ERR_UPLOAD_FILE_CHANGED;
291
292 if (result > 0) {
293 DCHECK_GE(bytes_remaining_, static_cast<uint64_t>(result));
294 bytes_remaining_ -= result;
295 }
296
297 return result;
298 }
299
OnIOComplete(int result)300 void UploadFileElementReader::OnIOComplete(int result) {
301 DCHECK(pending_callback_);
302
303 result = DoLoop(result);
304
305 if (result != ERR_IO_PENDING)
306 std::move(pending_callback_).Run(result);
307 }
308
309 UploadFileElementReader::ScopedOverridingContentLengthForTests::
ScopedOverridingContentLengthForTests(uint64_t value)310 ScopedOverridingContentLengthForTests(uint64_t value) {
311 overriding_content_length = value;
312 }
313
314 UploadFileElementReader::ScopedOverridingContentLengthForTests::
~ScopedOverridingContentLengthForTests()315 ~ScopedOverridingContentLengthForTests() {
316 overriding_content_length = 0;
317 }
318
319 } // namespace net
320