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/http/partial_data.h"
6
7 #include <limits>
8 #include <utility>
9
10 #include "base/format_macros.h"
11 #include "base/functional/bind.h"
12 #include "base/functional/callback_helpers.h"
13 #include "base/logging.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "net/base/net_errors.h"
18 #include "net/disk_cache/disk_cache.h"
19 #include "net/http/http_response_headers.h"
20 #include "net/http/http_status_code.h"
21 #include "net/http/http_util.h"
22
23 namespace net {
24
25 namespace {
26
27 // The headers that we have to process.
28 const char kLengthHeader[] = "Content-Length";
29 const char kRangeHeader[] = "Content-Range";
30 const int kDataStream = 1;
31
32 } // namespace
33
34 PartialData::PartialData() = default;
35
36 PartialData::~PartialData() = default;
37
Init(const HttpRequestHeaders & headers)38 bool PartialData::Init(const HttpRequestHeaders& headers) {
39 std::optional<std::string> range_header =
40 headers.GetHeader(HttpRequestHeaders::kRange);
41 if (!range_header) {
42 range_requested_ = false;
43 return false;
44 }
45 range_requested_ = true;
46
47 std::vector<HttpByteRange> ranges;
48 if (!HttpUtil::ParseRangeHeader(range_header.value(), &ranges) ||
49 ranges.size() != 1) {
50 return false;
51 }
52
53 // We can handle this range request.
54 byte_range_ = ranges[0];
55 user_byte_range_ = byte_range_;
56 if (!byte_range_.IsValid())
57 return false;
58
59 current_range_start_ = byte_range_.first_byte_position();
60
61 DVLOG(1) << "Range start: " << current_range_start_ << " end: " <<
62 byte_range_.last_byte_position();
63 return true;
64 }
65
SetHeaders(const HttpRequestHeaders & headers)66 void PartialData::SetHeaders(const HttpRequestHeaders& headers) {
67 DCHECK(extra_headers_.IsEmpty());
68 extra_headers_ = headers;
69 }
70
RestoreHeaders(HttpRequestHeaders * headers) const71 void PartialData::RestoreHeaders(HttpRequestHeaders* headers) const {
72 DCHECK(current_range_start_ >= 0 || byte_range_.IsSuffixByteRange());
73 int64_t end = byte_range_.IsSuffixByteRange()
74 ? byte_range_.suffix_length()
75 : byte_range_.last_byte_position();
76
77 *headers = extra_headers_;
78 if (truncated_ || !byte_range_.IsValid())
79 return;
80
81 if (current_range_start_ < 0) {
82 headers->SetHeader(HttpRequestHeaders::kRange,
83 HttpByteRange::Suffix(end).GetHeaderValue());
84 } else {
85 headers->SetHeader(HttpRequestHeaders::kRange,
86 HttpByteRange::Bounded(
87 current_range_start_, end).GetHeaderValue());
88 }
89 }
90
ShouldValidateCache(disk_cache::Entry * entry,CompletionOnceCallback callback)91 int PartialData::ShouldValidateCache(disk_cache::Entry* entry,
92 CompletionOnceCallback callback) {
93 DCHECK_GE(current_range_start_, 0);
94
95 // Scan the disk cache for the first cached portion within this range.
96 int len = GetNextRangeLen();
97 if (!len)
98 return 0;
99
100 DVLOG(3) << "ShouldValidateCache len: " << len;
101
102 if (sparse_entry_) {
103 DCHECK(callback_.is_null());
104 disk_cache::RangeResultCallback cb = base::BindOnce(
105 &PartialData::GetAvailableRangeCompleted, weak_factory_.GetWeakPtr());
106 disk_cache::RangeResult range =
107 entry->GetAvailableRange(current_range_start_, len, std::move(cb));
108
109 cached_min_len_ =
110 range.net_error == OK ? range.available_len : range.net_error;
111 if (cached_min_len_ == ERR_IO_PENDING) {
112 callback_ = std::move(callback);
113 return ERR_IO_PENDING;
114 } else {
115 cached_start_ = range.start;
116 }
117 } else if (!truncated_) {
118 if (byte_range_.HasFirstBytePosition() &&
119 byte_range_.first_byte_position() >= resource_size_) {
120 // The caller should take care of this condition because we should have
121 // failed IsRequestedRangeOK(), but it's better to be consistent here.
122 len = 0;
123 }
124 cached_min_len_ = len;
125 cached_start_ = current_range_start_;
126 }
127
128 if (cached_min_len_ < 0)
129 return cached_min_len_;
130
131 // Return a positive number to indicate success (versus error or finished).
132 return 1;
133 }
134
PrepareCacheValidation(disk_cache::Entry * entry,HttpRequestHeaders * headers)135 void PartialData::PrepareCacheValidation(disk_cache::Entry* entry,
136 HttpRequestHeaders* headers) {
137 DCHECK_GE(current_range_start_, 0);
138 DCHECK_GE(cached_min_len_, 0);
139
140 int len = GetNextRangeLen();
141 if (!len) {
142 // Stored body is empty, so just use the original range header.
143 headers->SetHeader(HttpRequestHeaders::kRange,
144 user_byte_range_.GetHeaderValue());
145 return;
146 }
147 range_present_ = false;
148
149 *headers = extra_headers_;
150
151 if (!cached_min_len_) {
152 // We don't have anything else stored.
153 final_range_ = true;
154 cached_start_ =
155 byte_range_.HasLastBytePosition() ? current_range_start_ + len : 0;
156 }
157
158 if (current_range_start_ == cached_start_) {
159 // The data lives in the cache.
160 range_present_ = true;
161 current_range_end_ = cached_start_ + cached_min_len_ - 1;
162 if (len == cached_min_len_)
163 final_range_ = true;
164 } else {
165 // This range is not in the cache.
166 current_range_end_ = cached_start_ - 1;
167 }
168 headers->SetHeader(
169 HttpRequestHeaders::kRange,
170 HttpByteRange::Bounded(current_range_start_, current_range_end_)
171 .GetHeaderValue());
172 }
173
IsCurrentRangeCached() const174 bool PartialData::IsCurrentRangeCached() const {
175 return range_present_;
176 }
177
IsLastRange() const178 bool PartialData::IsLastRange() const {
179 return final_range_;
180 }
181
UpdateFromStoredHeaders(const HttpResponseHeaders * headers,disk_cache::Entry * entry,bool truncated,bool writing_in_progress)182 bool PartialData::UpdateFromStoredHeaders(const HttpResponseHeaders* headers,
183 disk_cache::Entry* entry,
184 bool truncated,
185 bool writing_in_progress) {
186 resource_size_ = 0;
187 if (truncated) {
188 DCHECK_EQ(headers->response_code(), 200);
189 // We don't have the real length and the user may be trying to create a
190 // sparse entry so let's not write to this entry.
191 if (byte_range_.IsValid())
192 return false;
193
194 if (!headers->HasStrongValidators())
195 return false;
196
197 // Now we avoid resume if there is no content length, but that was not
198 // always the case so double check here.
199 int64_t total_length = headers->GetContentLength();
200 if (total_length <= 0)
201 return false;
202
203 // In case we see a truncated entry, we first send a network request for
204 // 1 byte range with If-Range: to probe server support for resumption.
205 // The setting of |current_range_start_| and |cached_start_| below (with any
206 // positive value of |cached_min_len_|) results in that.
207 //
208 // Setting |initial_validation_| to true is how this communicates to
209 // HttpCache::Transaction that we're doing that (and that it's not the user
210 // asking for one byte), so if it sees a 206 with that flag set it will call
211 // SetRangeToStartDownload(), and then restart the process looking for the
212 // entire file (which is what the user wanted), with the cache handling
213 // the previous portion, and then a second network request for the entire
214 // rest of the range. A 200 in response to the probe request can be simply
215 // returned directly to the user.
216 truncated_ = true;
217 initial_validation_ = true;
218 sparse_entry_ = false;
219 int current_len = entry->GetDataSize(kDataStream);
220 byte_range_.set_first_byte_position(current_len);
221 resource_size_ = total_length;
222 current_range_start_ = current_len;
223 cached_min_len_ = current_len;
224 cached_start_ = current_len + 1;
225 return true;
226 }
227
228 sparse_entry_ = (headers->response_code() == HTTP_PARTIAL_CONTENT);
229
230 if (writing_in_progress || sparse_entry_) {
231 // |writing_in_progress| means another Transaction is still fetching the
232 // body, so the only way we can see the length is if the server sent it
233 // in Content-Length -- GetDataSize would just return what got written
234 // thus far.
235 //
236 // |sparse_entry_| means a 206, and for those FixContentLength arranges it
237 // so that Content-Length written to the cache has the full length (on wire
238 // it's for a particular range only); while GetDataSize would be unusable
239 // since the data is stored using WriteSparseData, and not in the usual data
240 // stream.
241 resource_size_ = headers->GetContentLength();
242 if (resource_size_ <= 0)
243 return false;
244 } else {
245 // If we can safely use GetDataSize, it's preferrable since it's usable for
246 // things w/o Content-Length, such as chunked content.
247 resource_size_ = entry->GetDataSize(kDataStream);
248 }
249
250 DVLOG(2) << "UpdateFromStoredHeaders size: " << resource_size_;
251
252 if (sparse_entry_) {
253 // If our previous is a 206, we need strong validators as we may be
254 // stiching the cached data and network data together.
255 if (!headers->HasStrongValidators())
256 return false;
257 // Make sure that this is really a sparse entry.
258 return entry->CouldBeSparse();
259 }
260 return true;
261 }
262
SetRangeToStartDownload()263 void PartialData::SetRangeToStartDownload() {
264 DCHECK(truncated_);
265 DCHECK(!sparse_entry_);
266 current_range_start_ = 0;
267 cached_start_ = 0;
268 initial_validation_ = false;
269 }
270
IsRequestedRangeOK()271 bool PartialData::IsRequestedRangeOK() {
272 if (byte_range_.IsValid()) {
273 if (!byte_range_.ComputeBounds(resource_size_))
274 return false;
275 if (truncated_)
276 return true;
277
278 if (current_range_start_ < 0)
279 current_range_start_ = byte_range_.first_byte_position();
280 } else {
281 // This is not a range request but we have partial data stored.
282 current_range_start_ = 0;
283 byte_range_.set_last_byte_position(resource_size_ - 1);
284 }
285
286 bool rv = current_range_start_ >= 0;
287 if (!rv)
288 current_range_start_ = 0;
289
290 return rv;
291 }
292
ResponseHeadersOK(const HttpResponseHeaders * headers)293 bool PartialData::ResponseHeadersOK(const HttpResponseHeaders* headers) {
294 if (headers->response_code() == HTTP_NOT_MODIFIED) {
295 if (!byte_range_.IsValid() || truncated_)
296 return true;
297
298 // We must have a complete range here.
299 return byte_range_.HasFirstBytePosition() &&
300 byte_range_.HasLastBytePosition();
301 }
302
303 int64_t start, end, total_length;
304 if (!headers->GetContentRangeFor206(&start, &end, &total_length))
305 return false;
306 if (total_length <= 0)
307 return false;
308
309 DCHECK_EQ(headers->response_code(), 206);
310
311 // A server should return a valid content length with a 206 (per the standard)
312 // but relax the requirement because some servers don't do that.
313 int64_t content_length = headers->GetContentLength();
314 if (content_length > 0 && content_length != end - start + 1)
315 return false;
316
317 if (!resource_size_) {
318 // First response. Update our values with the ones provided by the server.
319 resource_size_ = total_length;
320 if (!byte_range_.HasFirstBytePosition()) {
321 byte_range_.set_first_byte_position(start);
322 current_range_start_ = start;
323 }
324 if (!byte_range_.HasLastBytePosition())
325 byte_range_.set_last_byte_position(end);
326 } else if (resource_size_ != total_length) {
327 return false;
328 }
329
330 if (truncated_) {
331 if (!byte_range_.HasLastBytePosition())
332 byte_range_.set_last_byte_position(end);
333 }
334
335 if (start != current_range_start_)
336 return false;
337
338 if (!current_range_end_) {
339 // There is nothing in the cache.
340 DCHECK(byte_range_.HasLastBytePosition());
341 current_range_end_ = byte_range_.last_byte_position();
342 if (current_range_end_ >= resource_size_) {
343 // We didn't know the real file size, and the server is saying that the
344 // requested range goes beyond the size. Fix it.
345 current_range_end_ = end;
346 byte_range_.set_last_byte_position(end);
347 }
348 }
349
350 // If we received a range, but it's not exactly the range we asked for, avoid
351 // trouble and signal an error.
352 if (end != current_range_end_)
353 return false;
354
355 return true;
356 }
357
358 // We are making multiple requests to complete the range requested by the user.
359 // Just assume that everything is fine and say that we are returning what was
360 // requested.
FixResponseHeaders(HttpResponseHeaders * headers,bool success)361 void PartialData::FixResponseHeaders(HttpResponseHeaders* headers,
362 bool success) {
363 if (truncated_)
364 return;
365
366 if (!success) {
367 headers->ReplaceStatusLine("HTTP/1.1 416 Requested Range Not Satisfiable");
368 headers->SetHeader(
369 kRangeHeader, base::StringPrintf("bytes 0-0/%" PRId64, resource_size_));
370 headers->SetHeader(kLengthHeader, "0");
371 return;
372 }
373
374 if (byte_range_.IsValid() && resource_size_) {
375 headers->UpdateWithNewRange(byte_range_, resource_size_, !sparse_entry_);
376 } else {
377 if (headers->response_code() == HTTP_PARTIAL_CONTENT) {
378 // TODO(rvargas): Is it safe to change the protocol version?
379 headers->ReplaceStatusLine("HTTP/1.1 200 OK");
380 }
381 headers->RemoveHeader(kRangeHeader);
382 headers->SetHeader(kLengthHeader,
383 base::StringPrintf("%" PRId64, resource_size_));
384 }
385 }
386
FixContentLength(HttpResponseHeaders * headers)387 void PartialData::FixContentLength(HttpResponseHeaders* headers) {
388 headers->SetHeader(kLengthHeader,
389 base::StringPrintf("%" PRId64, resource_size_));
390 }
391
CacheRead(disk_cache::Entry * entry,IOBuffer * data,int data_len,CompletionOnceCallback callback)392 int PartialData::CacheRead(disk_cache::Entry* entry,
393 IOBuffer* data,
394 int data_len,
395 CompletionOnceCallback callback) {
396 int read_len = std::min(data_len, cached_min_len_);
397 if (!read_len)
398 return 0;
399
400 int rv = 0;
401 if (sparse_entry_) {
402 rv = entry->ReadSparseData(current_range_start_, data, read_len,
403 std::move(callback));
404 } else {
405 if (current_range_start_ > std::numeric_limits<int32_t>::max())
406 return ERR_INVALID_ARGUMENT;
407
408 rv = entry->ReadData(kDataStream, static_cast<int>(current_range_start_),
409 data, read_len, std::move(callback));
410 }
411 return rv;
412 }
413
CacheWrite(disk_cache::Entry * entry,IOBuffer * data,int data_len,CompletionOnceCallback callback)414 int PartialData::CacheWrite(disk_cache::Entry* entry,
415 IOBuffer* data,
416 int data_len,
417 CompletionOnceCallback callback) {
418 DVLOG(3) << "To write: " << data_len;
419 if (sparse_entry_) {
420 return entry->WriteSparseData(current_range_start_, data, data_len,
421 std::move(callback));
422 } else {
423 if (current_range_start_ > std::numeric_limits<int32_t>::max())
424 return ERR_INVALID_ARGUMENT;
425
426 return entry->WriteData(kDataStream, static_cast<int>(current_range_start_),
427 data, data_len, std::move(callback), true);
428 }
429 }
430
OnCacheReadCompleted(int result)431 void PartialData::OnCacheReadCompleted(int result) {
432 DVLOG(3) << "Read: " << result;
433 if (result > 0) {
434 current_range_start_ += result;
435 cached_min_len_ -= result;
436 DCHECK_GE(cached_min_len_, 0);
437 }
438 }
439
OnNetworkReadCompleted(int result)440 void PartialData::OnNetworkReadCompleted(int result) {
441 if (result > 0)
442 current_range_start_ += result;
443 }
444
GetNextRangeLen()445 int PartialData::GetNextRangeLen() {
446 if (!resource_size_) {
447 return 0;
448 }
449 int64_t range_len =
450 byte_range_.HasLastBytePosition()
451 ? byte_range_.last_byte_position() - current_range_start_ + 1
452 : std::numeric_limits<int32_t>::max();
453 if (range_len > std::numeric_limits<int32_t>::max())
454 range_len = std::numeric_limits<int32_t>::max();
455 return static_cast<int32_t>(range_len);
456 }
457
GetAvailableRangeCompleted(const disk_cache::RangeResult & result)458 void PartialData::GetAvailableRangeCompleted(
459 const disk_cache::RangeResult& result) {
460 DCHECK(!callback_.is_null());
461 DCHECK_NE(ERR_IO_PENDING, result.net_error);
462
463 int len_or_error =
464 result.net_error == OK ? result.available_len : result.net_error;
465 cached_start_ = result.start;
466 cached_min_len_ = len_or_error;
467
468 // ShouldValidateCache has an unusual convention where 0 denotes EOF,
469 // so convert end of range to success (since there may be things that need
470 // fetching from network or other ranges).
471 std::move(callback_).Run(len_or_error >= 0 ? 1 : len_or_error);
472 }
473
474 } // namespace net
475