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.
5 #include "net/http/partial_data.h"
8 #include "base/bind_helpers.h"
9 #include "base/format_macros.h"
10 #include "base/logging.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "net/base/net_errors.h"
15 #include "net/disk_cache/disk_cache.h"
16 #include "net/http/http_response_headers.h"
17 #include "net/http/http_util.h"
23 // The headers that we have to process.
24 const char kLengthHeader
[] = "Content-Length";
25 const char kRangeHeader
[] = "Content-Range";
26 const int kDataStream
= 1;
28 void AddRangeHeader(int64 start
, int64 end
, HttpRequestHeaders
* headers
) {
29 DCHECK(start
>= 0 || end
>= 0);
30 std::string my_start
, my_end
;
32 my_start
= base::Int64ToString(start
);
34 my_end
= base::Int64ToString(end
);
37 HttpRequestHeaders::kRange
,
38 base::StringPrintf("bytes=%s-%s", my_start
.c_str(), my_end
.c_str()));
43 // A core object that can be detached from the Partialdata object at destruction
44 // so that asynchronous operations cleanup can be performed.
45 class PartialData::Core
{
47 // Build a new core object. Lifetime management is automatic.
48 static Core
* CreateCore(PartialData
* owner
) {
49 return new Core(owner
);
52 // Wrapper for Entry::GetAvailableRange. If this method returns ERR_IO_PENDING
53 // PartialData::GetAvailableRangeCompleted() will be invoked on the owner
54 // object when finished (unless Cancel() is called first).
55 int GetAvailableRange(disk_cache::Entry
* entry
, int64 offset
, int len
,
58 // Cancels a pending operation. It is a mistake to call this method if there
59 // is no operation in progress; in fact, there will be no object to do so.
63 explicit Core(PartialData
* owner
);
66 // Pending io completion routine.
67 void OnIOComplete(int result
);
72 DISALLOW_COPY_AND_ASSIGN(Core
);
75 PartialData::Core::Core(PartialData
* owner
)
76 : owner_(owner
), start_(0) {
77 DCHECK(!owner_
->core_
);
81 PartialData::Core::~Core() {
86 void PartialData::Core::Cancel() {
91 int PartialData::Core::GetAvailableRange(disk_cache::Entry
* entry
, int64 offset
,
92 int len
, int64
* start
) {
93 int rv
= entry
->GetAvailableRange(
94 offset
, len
, &start_
, base::Bind(&PartialData::Core::OnIOComplete
,
95 base::Unretained(this)));
96 if (rv
!= net::ERR_IO_PENDING
) {
97 // The callback will not be invoked. Lets cleanup.
104 void PartialData::Core::OnIOComplete(int result
) {
106 owner_
->GetAvailableRangeCompleted(result
, start_
);
110 // -----------------------------------------------------------------------------
112 PartialData::PartialData()
113 : range_present_(false),
117 initial_validation_(false),
121 PartialData::~PartialData() {
126 bool PartialData::Init(const HttpRequestHeaders
& headers
) {
127 std::string range_header
;
128 if (!headers
.GetHeader(HttpRequestHeaders::kRange
, &range_header
))
131 std::vector
<HttpByteRange
> ranges
;
132 if (!HttpUtil::ParseRangeHeader(range_header
, &ranges
) || ranges
.size() != 1)
135 // We can handle this range request.
136 byte_range_
= ranges
[0];
137 if (!byte_range_
.IsValid())
141 current_range_start_
= byte_range_
.first_byte_position();
143 DVLOG(1) << "Range start: " << current_range_start_
<< " end: " <<
144 byte_range_
.last_byte_position();
148 void PartialData::SetHeaders(const HttpRequestHeaders
& headers
) {
149 DCHECK(extra_headers_
.IsEmpty());
150 extra_headers_
.CopyFrom(headers
);
153 void PartialData::RestoreHeaders(HttpRequestHeaders
* headers
) const {
154 DCHECK(current_range_start_
>= 0 || byte_range_
.IsSuffixByteRange());
155 int64 end
= byte_range_
.IsSuffixByteRange() ?
156 byte_range_
.suffix_length() : byte_range_
.last_byte_position();
158 headers
->CopyFrom(extra_headers_
);
159 if (!truncated_
&& byte_range_
.IsValid())
160 AddRangeHeader(current_range_start_
, end
, headers
);
163 int PartialData::ShouldValidateCache(disk_cache::Entry
* entry
,
164 const CompletionCallback
& callback
) {
165 DCHECK_GE(current_range_start_
, 0);
167 // Scan the disk cache for the first cached portion within this range.
168 int len
= GetNextRangeLen();
172 DVLOG(3) << "ShouldValidateCache len: " << len
;
175 DCHECK(callback_
.is_null());
176 Core
* core
= Core::CreateCore(this);
177 cached_min_len_
= core
->GetAvailableRange(entry
, current_range_start_
, len
,
180 if (cached_min_len_
== ERR_IO_PENDING
) {
181 callback_
= callback
;
182 return ERR_IO_PENDING
;
184 } else if (!truncated_
) {
185 if (byte_range_
.HasFirstBytePosition() &&
186 byte_range_
.first_byte_position() >= resource_size_
) {
187 // The caller should take care of this condition because we should have
188 // failed IsRequestedRangeOK(), but it's better to be consistent here.
191 cached_min_len_
= len
;
192 cached_start_
= current_range_start_
;
195 if (cached_min_len_
< 0)
196 return cached_min_len_
;
198 // Return a positive number to indicate success (versus error or finished).
202 void PartialData::PrepareCacheValidation(disk_cache::Entry
* entry
,
203 HttpRequestHeaders
* headers
) {
204 DCHECK_GE(current_range_start_
, 0);
205 DCHECK_GE(cached_min_len_
, 0);
207 int len
= GetNextRangeLen();
209 range_present_
= false;
211 headers
->CopyFrom(extra_headers_
);
213 if (!cached_min_len_
) {
214 // We don't have anything else stored.
217 byte_range_
.HasLastBytePosition() ? current_range_start_
+ len
: 0;
220 if (current_range_start_
== cached_start_
) {
221 // The data lives in the cache.
222 range_present_
= true;
223 if (len
== cached_min_len_
)
225 AddRangeHeader(current_range_start_
, cached_start_
+ cached_min_len_
- 1,
228 // This range is not in the cache.
229 AddRangeHeader(current_range_start_
, cached_start_
- 1, headers
);
233 bool PartialData::IsCurrentRangeCached() const {
234 return range_present_
;
237 bool PartialData::IsLastRange() const {
241 bool PartialData::UpdateFromStoredHeaders(const HttpResponseHeaders
* headers
,
242 disk_cache::Entry
* entry
,
246 DCHECK_EQ(headers
->response_code(), 200);
247 // We don't have the real length and the user may be trying to create a
248 // sparse entry so let's not write to this entry.
249 if (byte_range_
.IsValid())
252 if (!headers
->HasStrongValidators())
255 // Now we avoid resume if there is no content length, but that was not
256 // always the case so double check here.
257 int64 total_length
= headers
->GetContentLength();
258 if (total_length
<= 0)
262 initial_validation_
= true;
263 sparse_entry_
= false;
264 int current_len
= entry
->GetDataSize(kDataStream
);
265 byte_range_
.set_first_byte_position(current_len
);
266 resource_size_
= total_length
;
267 current_range_start_
= current_len
;
268 cached_min_len_
= current_len
;
269 cached_start_
= current_len
+ 1;
273 if (headers
->response_code() != 206) {
274 DCHECK(byte_range_
.IsValid());
275 sparse_entry_
= false;
276 resource_size_
= entry
->GetDataSize(kDataStream
);
277 DVLOG(2) << "UpdateFromStoredHeaders size: " << resource_size_
;
281 if (!headers
->HasStrongValidators())
284 int64 length_value
= headers
->GetContentLength();
285 if (length_value
<= 0)
286 return false; // We must have stored the resource length.
288 resource_size_
= length_value
;
290 // Make sure that this is really a sparse entry.
291 return entry
->CouldBeSparse();
294 void PartialData::SetRangeToStartDownload() {
296 DCHECK(!sparse_entry_
);
297 current_range_start_
= 0;
299 initial_validation_
= false;
302 bool PartialData::IsRequestedRangeOK() {
303 if (byte_range_
.IsValid()) {
304 if (!byte_range_
.ComputeBounds(resource_size_
))
309 if (current_range_start_
< 0)
310 current_range_start_
= byte_range_
.first_byte_position();
312 // This is not a range request but we have partial data stored.
313 current_range_start_
= 0;
314 byte_range_
.set_last_byte_position(resource_size_
- 1);
317 bool rv
= current_range_start_
>= 0;
319 current_range_start_
= 0;
324 bool PartialData::ResponseHeadersOK(const HttpResponseHeaders
* headers
) {
325 if (headers
->response_code() == 304) {
326 if (!byte_range_
.IsValid() || truncated_
)
329 // We must have a complete range here.
330 return byte_range_
.HasFirstBytePosition() &&
331 byte_range_
.HasLastBytePosition();
334 int64 start
, end
, total_length
;
335 if (!headers
->GetContentRange(&start
, &end
, &total_length
))
337 if (total_length
<= 0)
340 int64 content_length
= headers
->GetContentLength();
341 if (content_length
< 0 || content_length
!= end
- start
+ 1)
344 if (!resource_size_
) {
345 // First response. Update our values with the ones provided by the server.
346 resource_size_
= total_length
;
347 if (!byte_range_
.HasFirstBytePosition()) {
348 byte_range_
.set_first_byte_position(start
);
349 current_range_start_
= start
;
351 if (!byte_range_
.HasLastBytePosition())
352 byte_range_
.set_last_byte_position(end
);
353 } else if (resource_size_
!= total_length
) {
358 if (!byte_range_
.HasLastBytePosition())
359 byte_range_
.set_last_byte_position(end
);
362 if (start
!= current_range_start_
)
365 if (byte_range_
.IsValid() && end
> byte_range_
.last_byte_position())
371 // We are making multiple requests to complete the range requested by the user.
372 // Just assume that everything is fine and say that we are returning what was
374 void PartialData::FixResponseHeaders(HttpResponseHeaders
* headers
,
379 headers
->RemoveHeader(kLengthHeader
);
380 headers
->RemoveHeader(kRangeHeader
);
382 int64 range_len
, start
, end
;
383 if (byte_range_
.IsValid()) {
386 headers
->ReplaceStatusLine("HTTP/1.1 206 Partial Content");
388 DCHECK(byte_range_
.HasFirstBytePosition());
389 DCHECK(byte_range_
.HasLastBytePosition());
390 start
= byte_range_
.first_byte_position();
391 end
= byte_range_
.last_byte_position();
392 range_len
= end
- start
+ 1;
394 headers
->ReplaceStatusLine(
395 "HTTP/1.1 416 Requested Range Not Satisfiable");
402 base::StringPrintf("%s: bytes %" PRId64
"-%" PRId64
"/%" PRId64
,
403 kRangeHeader
, start
, end
, resource_size_
));
405 // TODO(rvargas): Is it safe to change the protocol version?
406 headers
->ReplaceStatusLine("HTTP/1.1 200 OK");
407 DCHECK_NE(resource_size_
, 0);
408 range_len
= resource_size_
;
411 headers
->AddHeader(base::StringPrintf("%s: %" PRId64
, kLengthHeader
,
415 void PartialData::FixContentLength(HttpResponseHeaders
* headers
) {
416 headers
->RemoveHeader(kLengthHeader
);
417 headers
->AddHeader(base::StringPrintf("%s: %" PRId64
, kLengthHeader
,
421 int PartialData::CacheRead(
422 disk_cache::Entry
* entry
, IOBuffer
* data
, int data_len
,
423 const net::CompletionCallback
& callback
) {
424 int read_len
= std::min(data_len
, cached_min_len_
);
430 rv
= entry
->ReadSparseData(current_range_start_
, data
, read_len
,
433 if (current_range_start_
> kint32max
)
434 return ERR_INVALID_ARGUMENT
;
436 rv
= entry
->ReadData(kDataStream
, static_cast<int>(current_range_start_
),
437 data
, read_len
, callback
);
442 int PartialData::CacheWrite(
443 disk_cache::Entry
* entry
, IOBuffer
* data
, int data_len
,
444 const net::CompletionCallback
& callback
) {
445 DVLOG(3) << "To write: " << data_len
;
447 return entry
->WriteSparseData(
448 current_range_start_
, data
, data_len
, callback
);
450 if (current_range_start_
> kint32max
)
451 return ERR_INVALID_ARGUMENT
;
453 return entry
->WriteData(kDataStream
, static_cast<int>(current_range_start_
),
454 data
, data_len
, callback
, true);
458 void PartialData::OnCacheReadCompleted(int result
) {
459 DVLOG(3) << "Read: " << result
;
461 current_range_start_
+= result
;
462 cached_min_len_
-= result
;
463 DCHECK_GE(cached_min_len_
, 0);
467 void PartialData::OnNetworkReadCompleted(int result
) {
469 current_range_start_
+= result
;
472 int PartialData::GetNextRangeLen() {
474 byte_range_
.HasLastBytePosition() ?
475 byte_range_
.last_byte_position() - current_range_start_
+ 1 :
477 if (range_len
> kint32max
)
478 range_len
= kint32max
;
479 return static_cast<int32
>(range_len
);
482 void PartialData::GetAvailableRangeCompleted(int result
, int64 start
) {
483 DCHECK(!callback_
.is_null());
484 DCHECK_NE(ERR_IO_PENDING
, result
);
486 cached_start_
= start
;
487 cached_min_len_
= result
;
489 result
= 1; // Return success, go ahead and validate the entry.
491 CompletionCallback cb
= callback_
;