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