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