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