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 case State::OPEN:
158 // Ignore previous result here. It's typically OK, but if Init()
159 // interrupted the previous operation, it may be an error.
160 result = DoOpen();
161 break;
162 case State::OPEN_COMPLETE:
163 result = DoOpenComplete(result);
164 break;
165 case State::SEEK:
166 DCHECK_EQ(OK, result);
167 result = DoSeek();
168 break;
169 case State::GET_FILE_INFO:
170 result = DoGetFileInfo(result);
171 break;
172 case State::GET_FILE_INFO_COMPLETE:
173 result = DoGetFileInfoComplete(result);
174 break;
175
176 case State::READ_COMPLETE:
177 result = DoReadComplete(result);
178 break;
179 }
180 }
181
182 return result;
183 }
184
DoOpen()185 int UploadFileElementReader::DoOpen() {
186 DCHECK(!file_stream_);
187
188 next_state_ = State::OPEN_COMPLETE;
189
190 file_stream_ = std::make_unique<FileStream>(task_runner_.get());
191 int result = file_stream_->Open(
192 path_,
193 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC,
194 base::BindOnce(&UploadFileElementReader::OnIOComplete,
195 weak_ptr_factory_.GetWeakPtr()));
196 DCHECK_GT(0, result);
197 return result;
198 }
199
DoOpenComplete(int result)200 int UploadFileElementReader::DoOpenComplete(int result) {
201 if (result < 0) {
202 DLOG(WARNING) << "Failed to open \"" << path_.value()
203 << "\" for reading: " << result;
204 file_stream_.reset();
205 return result;
206 }
207
208 if (range_offset_) {
209 next_state_ = State::SEEK;
210 } else {
211 next_state_ = State::GET_FILE_INFO;
212 }
213 return net::OK;
214 }
215
DoSeek()216 int UploadFileElementReader::DoSeek() {
217 next_state_ = State::GET_FILE_INFO;
218 return file_stream_->Seek(
219 range_offset_,
220 base::BindOnce(
221 [](base::WeakPtr<UploadFileElementReader> weak_this, int64_t result) {
222 if (!weak_this)
223 return;
224 weak_this->OnIOComplete(result >= 0 ? OK
225 : static_cast<int>(result));
226 },
227 weak_ptr_factory_.GetWeakPtr()));
228 }
229
DoGetFileInfo(int result)230 int UploadFileElementReader::DoGetFileInfo(int result) {
231 if (result < 0) {
232 DLOG(WARNING) << "Failed to seek \"" << path_.value()
233 << "\" to offset: " << range_offset_ << " (" << result << ")";
234 return result;
235 }
236
237 next_state_ = State::GET_FILE_INFO_COMPLETE;
238
239 auto file_info = std::make_unique<base::File::Info>();
240 auto* file_info_ptr = file_info.get();
241 result = file_stream_->GetFileInfo(
242 file_info_ptr,
243 base::BindOnce(
244 [](base::WeakPtr<UploadFileElementReader> weak_this,
245 std::unique_ptr<base::File::Info> file_info, int result) {
246 if (!weak_this)
247 return;
248 weak_this->file_info_ = *file_info;
249 weak_this->OnIOComplete(result);
250 },
251 weak_ptr_factory_.GetWeakPtr(), std::move(file_info)));
252 // GetFileInfo() can't succeed synchronously.
253 DCHECK_NE(result, OK);
254 return result;
255 }
256
DoGetFileInfoComplete(int result)257 int UploadFileElementReader::DoGetFileInfoComplete(int result) {
258 if (result != OK) {
259 DLOG(WARNING) << "Failed to get file info of \"" << path_.value() << "\"";
260 return result;
261 }
262
263 int64_t length = file_info_.size;
264 if (range_offset_ < static_cast<uint64_t>(length)) {
265 // Compensate for the offset.
266 length = std::min(length - range_offset_, range_length_);
267 }
268
269 // If the underlying file has been changed and the expected file modification
270 // time is set, treat it as error. Note that |expected_modification_time_| may
271 // have gone through multiple conversion steps involving loss of precision
272 // (including conversion to time_t). Therefore the check below only verifies
273 // that the timestamps are within one second of each other. This check is used
274 // for sliced files.
275 if (!expected_modification_time_.is_null() &&
276 (expected_modification_time_ - file_info_.last_modified)
277 .magnitude()
278 .InSeconds() != 0) {
279 return ERR_UPLOAD_FILE_CHANGED;
280 }
281
282 content_length_ = length;
283 bytes_remaining_ = GetContentLength();
284 return result;
285 }
286
DoReadComplete(int result)287 int UploadFileElementReader::DoReadComplete(int result) {
288 if (result == 0) // Reached end-of-file earlier than expected.
289 return ERR_UPLOAD_FILE_CHANGED;
290
291 if (result > 0) {
292 DCHECK_GE(bytes_remaining_, static_cast<uint64_t>(result));
293 bytes_remaining_ -= result;
294 }
295
296 return result;
297 }
298
OnIOComplete(int result)299 void UploadFileElementReader::OnIOComplete(int result) {
300 DCHECK(pending_callback_);
301
302 result = DoLoop(result);
303
304 if (result != ERR_IO_PENDING)
305 std::move(pending_callback_).Run(result);
306 }
307
308 UploadFileElementReader::ScopedOverridingContentLengthForTests::
ScopedOverridingContentLengthForTests(uint64_t value)309 ScopedOverridingContentLengthForTests(uint64_t value) {
310 overriding_content_length = value;
311 }
312
313 UploadFileElementReader::ScopedOverridingContentLengthForTests::
~ScopedOverridingContentLengthForTests()314 ~ScopedOverridingContentLengthForTests() {
315 overriding_content_length = 0;
316 }
317
318 } // namespace net
319