1 // Copyright 2015 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 "media/blink/resource_multibuffer_data_provider.h"
6
7 #include <stddef.h>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/bits.h"
12 #include "base/callback_helpers.h"
13 #include "base/location.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "media/blink/cache_util.h"
20 #include "media/blink/media_blink_export.h"
21 #include "media/blink/resource_fetch_context.h"
22 #include "media/blink/url_index.h"
23 #include "net/http/http_byte_range.h"
24 #include "net/http/http_request_headers.h"
25 #include "services/network/public/cpp/cors/cors.h"
26 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
27 #include "third_party/blink/public/platform/web_network_state_notifier.h"
28 #include "third_party/blink/public/platform/web_url.h"
29 #include "third_party/blink/public/platform/web_url_error.h"
30 #include "third_party/blink/public/platform/web_url_response.h"
31 #include "third_party/blink/public/web/web_associated_url_loader.h"
32
33 using blink::WebAssociatedURLLoader;
34 using blink::WebString;
35 using blink::WebURLError;
36 using blink::WebURLRequest;
37 using blink::WebURLResponse;
38
39 namespace media {
40
41 // The number of milliseconds to wait before retrying a failed load.
42 const int kLoaderFailedRetryDelayMs = 250;
43
44 // Each retry, add this many MS to the delay.
45 // total delay is:
46 // (kLoaderPartialRetryDelayMs +
47 // kAdditionalDelayPerRetryMs * (kMaxRetries - 1) / 2) * kMaxretries = 29250 ms
48 const int kAdditionalDelayPerRetryMs = 50;
49
50 // The number of milliseconds to wait before retrying when the server
51 // decides to not give us all the data at once.
52 const int kLoaderPartialRetryDelayMs = 25;
53
54 const int kHttpOK = 200;
55 const int kHttpPartialContent = 206;
56 const int kHttpRangeNotSatisfiable = 416;
57
ResourceMultiBufferDataProvider(UrlData * url_data,MultiBufferBlockId pos,bool is_client_audio_element)58 ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider(
59 UrlData* url_data,
60 MultiBufferBlockId pos,
61 bool is_client_audio_element)
62 : pos_(pos),
63 url_data_(url_data),
64 retries_(0),
65 cors_mode_(url_data->cors_mode()),
66 origin_(url_data->url().GetOrigin()),
67 is_client_audio_element_(is_client_audio_element) {
68 DCHECK(url_data_) << " pos = " << pos;
69 DCHECK_GE(pos, 0);
70 }
71
72 ResourceMultiBufferDataProvider::~ResourceMultiBufferDataProvider() = default;
73
Start()74 void ResourceMultiBufferDataProvider::Start() {
75 DVLOG(1) << __func__ << " @ " << byte_pos();
76 if (url_data_->length() > 0 && byte_pos() >= url_data_->length()) {
77 base::ThreadTaskRunnerHandle::Get()->PostTask(
78 FROM_HERE, base::BindOnce(&ResourceMultiBufferDataProvider::Terminate,
79 weak_factory_.GetWeakPtr()));
80 return;
81 }
82
83 // Prepare the request.
84 WebURLRequest request(url_data_->url());
85 request.SetRequestContext(is_client_audio_element_
86 ? blink::mojom::RequestContextType::AUDIO
87 : blink::mojom::RequestContextType::VIDEO);
88 request.SetRequestDestination(
89 is_client_audio_element_ ? network::mojom::RequestDestination::kAudio
90 : network::mojom::RequestDestination::kVideo);
91 request.SetHttpHeaderField(
92 WebString::FromUTF8(net::HttpRequestHeaders::kRange),
93 WebString::FromUTF8(
94 net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue()));
95
96 if (url_data_->length() == kPositionNotSpecified &&
97 url_data_->CachedSize() == 0 && url_data_->BytesReadFromCache() == 0 &&
98 blink::WebNetworkStateNotifier::SaveDataEnabled() &&
99 url_data_->url().SchemeIs(url::kHttpScheme)) {
100 // This lets the data reduction proxy know that we don't have anything
101 // previously cached data for this resource. We can only send it if this is
102 // the first request for this resource.
103 request.SetHttpHeaderField(WebString::FromUTF8("chrome-proxy"),
104 WebString::FromUTF8("frfr"));
105 }
106
107 // We would like to send an if-match header with the request to
108 // tell the remote server that we really can't handle files other
109 // than the one we already started playing. Unfortunately, doing
110 // so will disable the http cache, and possibly other proxies
111 // along the way. See crbug/504194 and crbug/689989 for more information.
112
113 // Disable compression, compression for audio/video doesn't make sense...
114 request.SetHttpHeaderField(
115 WebString::FromUTF8(net::HttpRequestHeaders::kAcceptEncoding),
116 WebString::FromUTF8("identity;q=1, *;q=0"));
117
118 // Start resource loading.
119 blink::WebAssociatedURLLoaderOptions options;
120 if (url_data_->cors_mode() != UrlData::CORS_UNSPECIFIED) {
121 options.expose_all_response_headers = true;
122 // The author header set is empty, no preflight should go ahead.
123 options.preflight_policy =
124 network::mojom::CorsPreflightPolicy::kPreventPreflight;
125
126 request.SetMode(network::mojom::RequestMode::kCors);
127 if (url_data_->cors_mode() != UrlData::CORS_USE_CREDENTIALS) {
128 request.SetCredentialsMode(network::mojom::CredentialsMode::kSameOrigin);
129 }
130 }
131
132 active_loader_ =
133 url_data_->url_index()->fetch_context()->CreateUrlLoader(options);
134 active_loader_->LoadAsynchronously(request, this);
135 }
136
137 /////////////////////////////////////////////////////////////////////////////
138 // MultiBuffer::DataProvider implementation.
Tell() const139 MultiBufferBlockId ResourceMultiBufferDataProvider::Tell() const {
140 return pos_;
141 }
142
Available() const143 bool ResourceMultiBufferDataProvider::Available() const {
144 if (fifo_.empty())
145 return false;
146 if (fifo_.back()->end_of_stream())
147 return true;
148 if (fifo_.front()->data_size() == block_size())
149 return true;
150 return false;
151 }
152
AvailableBytes() const153 int64_t ResourceMultiBufferDataProvider::AvailableBytes() const {
154 int64_t bytes = 0;
155 for (const auto& i : fifo_) {
156 if (i->end_of_stream())
157 break;
158 bytes += i->data_size();
159 }
160 return bytes;
161 }
162
Read()163 scoped_refptr<DataBuffer> ResourceMultiBufferDataProvider::Read() {
164 DCHECK(Available());
165 scoped_refptr<DataBuffer> ret = fifo_.front();
166 fifo_.pop_front();
167 ++pos_;
168 return ret;
169 }
170
SetDeferred(bool deferred)171 void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) {
172 if (active_loader_)
173 active_loader_->SetDefersLoading(deferred);
174 }
175
176 /////////////////////////////////////////////////////////////////////////////
177 // WebAssociatedURLLoaderClient implementation.
178
WillFollowRedirect(const blink::WebURL & new_url,const WebURLResponse & redirect_response)179 bool ResourceMultiBufferDataProvider::WillFollowRedirect(
180 const blink::WebURL& new_url,
181 const WebURLResponse& redirect_response) {
182 DVLOG(1) << "willFollowRedirect";
183 redirects_to_ = new_url;
184 url_data_->set_valid_until(base::Time::Now() +
185 GetCacheValidUntil(redirect_response));
186
187 // This test is vital for security!
188 if (cors_mode_ == UrlData::CORS_UNSPECIFIED) {
189 // We allow the redirect if the origin is the same.
190 if (origin_ != redirects_to_.GetOrigin()) {
191 // We also allow the redirect if we don't have any data in the
192 // cache, as that means that no dangerous data mixing can occur.
193 if (url_data_->multibuffer()->map().empty() && fifo_.empty())
194 return true;
195
196 active_loader_.reset();
197 url_data_->Fail();
198 return false; // "this" may be deleted now.
199 }
200 }
201 return true;
202 }
203
DidSendData(uint64_t bytes_sent,uint64_t total_bytes_to_be_sent)204 void ResourceMultiBufferDataProvider::DidSendData(
205 uint64_t bytes_sent,
206 uint64_t total_bytes_to_be_sent) {
207 NOTIMPLEMENTED();
208 }
209
DidReceiveResponse(const WebURLResponse & response)210 void ResourceMultiBufferDataProvider::DidReceiveResponse(
211 const WebURLResponse& response) {
212 #if DCHECK_IS_ON()
213 std::string version;
214 switch (response.HttpVersion()) {
215 case WebURLResponse::kHTTPVersion_0_9:
216 version = "0.9";
217 break;
218 case WebURLResponse::kHTTPVersion_1_0:
219 version = "1.0";
220 break;
221 case WebURLResponse::kHTTPVersion_1_1:
222 version = "1.1";
223 break;
224 case WebURLResponse::kHTTPVersion_2_0:
225 version = "2.1";
226 break;
227 case WebURLResponse::kHTTPVersionUnknown:
228 version = "unknown";
229 break;
230 }
231 DVLOG(1) << "didReceiveResponse: HTTP/" << version << " "
232 << response.HttpStatusCode();
233 #endif
234 DCHECK(active_loader_);
235
236 scoped_refptr<UrlData> destination_url_data(url_data_);
237
238 if (!redirects_to_.is_empty()) {
239 destination_url_data =
240 url_data_->url_index()->GetByUrl(redirects_to_, cors_mode_);
241 redirects_to_ = GURL();
242 }
243
244 base::Time last_modified;
245 if (base::Time::FromString(
246 response.HttpHeaderField("Last-Modified").Utf8().data(),
247 &last_modified)) {
248 destination_url_data->set_last_modified(last_modified);
249 }
250
251 destination_url_data->set_etag(
252 response.HttpHeaderField("ETag").Utf8().data());
253
254 destination_url_data->set_valid_until(base::Time::Now() +
255 GetCacheValidUntil(response));
256
257 destination_url_data->set_cacheable(GetReasonsForUncacheability(response) ==
258 0);
259
260 // Expected content length can be |kPositionNotSpecified|, in that case
261 // |content_length_| is not specified and this is a streaming response.
262 int64_t content_length = response.ExpectedContentLength();
263 bool end_of_file = false;
264 bool do_fail = false;
265 // We get the response type here because aborting the loader may change it.
266 const auto response_type = response.GetType();
267 bytes_to_discard_ = 0;
268
269 // We make a strong assumption that when we reach here we have either
270 // received a response from HTTP/HTTPS protocol or the request was
271 // successful (in particular range request). So we only verify the partial
272 // response for HTTP and HTTPS protocol.
273 if (destination_url_data->url().SchemeIsHTTPOrHTTPS()) {
274 bool partial_response = (response.HttpStatusCode() == kHttpPartialContent);
275 bool ok_response = (response.HttpStatusCode() == kHttpOK);
276
277 // Check to see whether the server supports byte ranges.
278 std::string accept_ranges =
279 response.HttpHeaderField("Accept-Ranges").Utf8();
280 if (accept_ranges.find("bytes") != std::string::npos)
281 destination_url_data->set_range_supported();
282
283 // If we have verified the partial response and it is correct.
284 // It's also possible for a server to support range requests
285 // without advertising "Accept-Ranges: bytes".
286 if (partial_response &&
287 VerifyPartialResponse(response, destination_url_data)) {
288 destination_url_data->set_range_supported();
289 } else if (ok_response) {
290 // We accept a 200 response for a Range:0- request, trusting the
291 // Accept-Ranges header, because Apache thinks that's a reasonable thing
292 // to return.
293 destination_url_data->set_length(content_length);
294 bytes_to_discard_ = byte_pos();
295 } else if (response.HttpStatusCode() == kHttpRangeNotSatisfiable) {
296 // Unsatisfiable range
297 // Really, we should never request a range that doesn't exist, but
298 // if we do, let's handle it in a sane way.
299 // Note, we can't just call OnDataProviderEvent() here, because
300 // url_data_ hasn't been updated to the final destination yet.
301 end_of_file = true;
302 } else {
303 active_loader_.reset();
304 // Can't call fail until readers have been migrated to the new
305 // url data below.
306 do_fail = true;
307 }
308 } else {
309 destination_url_data->set_range_supported();
310 if (content_length != kPositionNotSpecified) {
311 destination_url_data->set_length(content_length + byte_pos());
312 }
313 }
314
315 if (!do_fail) {
316 destination_url_data =
317 url_data_->url_index()->TryInsert(destination_url_data);
318 }
319
320 // This is vital for security!
321 destination_url_data->set_is_cors_cross_origin(
322 network::cors::IsCorsCrossOriginResponseType(response_type));
323
324 // Only used for metrics.
325 {
326 WebString access_control =
327 response.HttpHeaderField("Access-Control-Allow-Origin");
328 if (!access_control.IsEmpty() && !access_control.Equals("null")) {
329 // Note: When |access_control| is not *, we should verify that it matches
330 // the requesting origin. Instead we just assume that it matches, which is
331 // probably accurate enough for metrics.
332 destination_url_data->set_has_access_control();
333 }
334 }
335
336 if (destination_url_data != url_data_) {
337 // At this point, we've encountered a redirect, or found a better url data
338 // instance for the data that we're about to download.
339
340 // First, let's take a ref on the current url data.
341 scoped_refptr<UrlData> old_url_data(url_data_);
342 destination_url_data->Use();
343
344 // Take ownership of ourselves. (From the multibuffer)
345 std::unique_ptr<DataProvider> self(
346 url_data_->multibuffer()->RemoveProvider(this));
347 url_data_ = destination_url_data.get();
348 // Give the ownership to our new owner.
349 url_data_->multibuffer()->AddProvider(std::move(self));
350
351 // Call callback to let upstream users know about the transfer.
352 // This will merge the data from the two multibuffers and
353 // cause clients to start using the new UrlData.
354 old_url_data->RedirectTo(destination_url_data);
355 }
356
357 if (do_fail) {
358 destination_url_data->Fail();
359 return; // "this" may be deleted now.
360 }
361
362 // Get the response URL since it can differ from the request URL when a
363 // service worker provided the response. Normally we would just use
364 // ResponseUrl(), but ResourceMultibufferDataProvider disallows mixing
365 // constructed responses (new Response()) and native server responses, even if
366 // they have the same response URL.
367 GURL response_url;
368 if (!response.WasFetchedViaServiceWorker() ||
369 response.HasUrlListViaServiceWorker()) {
370 response_url = response.ResponseUrl();
371 }
372
373 // This test is vital for security!
374 if (!url_data_->ValidateDataOrigin(response_url.GetOrigin())) {
375 active_loader_.reset();
376 url_data_->Fail();
377 return; // "this" may be deleted now.
378 }
379
380 if (end_of_file) {
381 fifo_.push_back(DataBuffer::CreateEOSBuffer());
382 url_data_->multibuffer()->OnDataProviderEvent(this);
383 }
384 }
385
DidReceiveData(const char * data,int data_length)386 void ResourceMultiBufferDataProvider::DidReceiveData(const char* data,
387 int data_length) {
388 DVLOG(1) << "didReceiveData: " << data_length << " bytes";
389 DCHECK(!Available());
390 DCHECK(active_loader_);
391 DCHECK_GT(data_length, 0);
392
393 if (bytes_to_discard_) {
394 uint64_t tmp = std::min<uint64_t>(bytes_to_discard_, data_length);
395 data_length -= tmp;
396 data += tmp;
397 bytes_to_discard_ -= tmp;
398 if (data_length == 0)
399 return;
400 }
401
402 // When we receive data, we allow more retries.
403 retries_ = 0;
404
405 while (data_length) {
406 if (fifo_.empty() || fifo_.back()->data_size() == block_size()) {
407 fifo_.push_back(new DataBuffer(block_size()));
408 fifo_.back()->set_data_size(0);
409 }
410 int last_block_size = fifo_.back()->data_size();
411 int to_append = std::min<int>(data_length, block_size() - last_block_size);
412 DCHECK_GT(to_append, 0);
413 memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append);
414 data += to_append;
415 fifo_.back()->set_data_size(last_block_size + to_append);
416 data_length -= to_append;
417 }
418
419 url_data_->multibuffer()->OnDataProviderEvent(this);
420
421 // Beware, this object might be deleted here.
422 }
423
DidDownloadData(uint64_t dataLength)424 void ResourceMultiBufferDataProvider::DidDownloadData(uint64_t dataLength) {
425 NOTIMPLEMENTED();
426 }
427
DidFinishLoading()428 void ResourceMultiBufferDataProvider::DidFinishLoading() {
429 DVLOG(1) << "didFinishLoading";
430 DCHECK(active_loader_.get());
431 DCHECK(!Available());
432
433 // We're done with the loader.
434 active_loader_.reset();
435
436 // If we didn't know the |instance_size_| we do now.
437 int64_t size = byte_pos();
438
439 // This request reports something smaller than what we've seen in the past,
440 // Maybe it's transient error?
441 if (url_data_->length() != kPositionNotSpecified &&
442 size < url_data_->length()) {
443 if (retries_ < kMaxRetries) {
444 DVLOG(1) << " Partial data received.... @ pos = " << size;
445 retries_++;
446 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
447 FROM_HERE,
448 base::BindOnce(&ResourceMultiBufferDataProvider::Start,
449 weak_factory_.GetWeakPtr()),
450 base::TimeDelta::FromMilliseconds(kLoaderPartialRetryDelayMs));
451 return;
452 } else {
453 url_data_->Fail();
454 return; // "this" may be deleted now.
455 }
456 }
457
458 url_data_->set_length(size);
459 fifo_.push_back(DataBuffer::CreateEOSBuffer());
460
461 if (url_data_->url_index()) {
462 url_data_->url_index()->TryInsert(url_data_);
463 }
464
465 DCHECK(Available());
466 url_data_->multibuffer()->OnDataProviderEvent(this);
467
468 // Beware, this object might be deleted here.
469 }
470
DidFail(const WebURLError & error)471 void ResourceMultiBufferDataProvider::DidFail(const WebURLError& error) {
472 DVLOG(1) << "didFail: reason=" << error.reason();
473 DCHECK(active_loader_.get());
474 active_loader_.reset();
475
476 if (retries_ < kMaxRetries && pos_ != 0) {
477 retries_++;
478 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
479 FROM_HERE,
480 base::BindOnce(&ResourceMultiBufferDataProvider::Start,
481 weak_factory_.GetWeakPtr()),
482 base::TimeDelta::FromMilliseconds(
483 kLoaderFailedRetryDelayMs + kAdditionalDelayPerRetryMs * retries_));
484 } else {
485 // We don't need to continue loading after failure.
486 // Note that calling Fail() will most likely delete this object.
487 url_data_->Fail();
488 }
489 }
490
ParseContentRange(const std::string & content_range_str,int64_t * first_byte_position,int64_t * last_byte_position,int64_t * instance_size)491 bool ResourceMultiBufferDataProvider::ParseContentRange(
492 const std::string& content_range_str,
493 int64_t* first_byte_position,
494 int64_t* last_byte_position,
495 int64_t* instance_size) {
496 const char kUpThroughBytesUnit[] = "bytes ";
497 if (!base::StartsWith(content_range_str, kUpThroughBytesUnit,
498 base::CompareCase::SENSITIVE)) {
499 return false;
500 }
501 std::string range_spec =
502 content_range_str.substr(sizeof(kUpThroughBytesUnit) - 1);
503 size_t dash_offset = range_spec.find("-");
504 size_t slash_offset = range_spec.find("/");
505
506 if (dash_offset == std::string::npos || slash_offset == std::string::npos ||
507 slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) {
508 return false;
509 }
510 if (!base::StringToInt64(range_spec.substr(0, dash_offset),
511 first_byte_position) ||
512 !base::StringToInt64(
513 range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1),
514 last_byte_position)) {
515 return false;
516 }
517 if (slash_offset == range_spec.length() - 2 &&
518 range_spec[slash_offset + 1] == '*') {
519 *instance_size = kPositionNotSpecified;
520 } else {
521 if (!base::StringToInt64(range_spec.substr(slash_offset + 1),
522 instance_size)) {
523 return false;
524 }
525 }
526 if (*last_byte_position < *first_byte_position ||
527 (*instance_size != kPositionNotSpecified &&
528 *last_byte_position >= *instance_size)) {
529 return false;
530 }
531
532 return true;
533 }
534
Terminate()535 void ResourceMultiBufferDataProvider::Terminate() {
536 fifo_.push_back(DataBuffer::CreateEOSBuffer());
537 url_data_->multibuffer()->OnDataProviderEvent(this);
538 }
539
byte_pos() const540 int64_t ResourceMultiBufferDataProvider::byte_pos() const {
541 int64_t ret = pos_;
542 ret += fifo_.size();
543 ret = ret << url_data_->multibuffer()->block_size_shift();
544 if (!fifo_.empty()) {
545 ret += fifo_.back()->data_size() - block_size();
546 }
547 return ret;
548 }
549
block_size() const550 int64_t ResourceMultiBufferDataProvider::block_size() const {
551 int64_t ret = 1;
552 return ret << url_data_->multibuffer()->block_size_shift();
553 }
554
VerifyPartialResponse(const WebURLResponse & response,const scoped_refptr<UrlData> & url_data)555 bool ResourceMultiBufferDataProvider::VerifyPartialResponse(
556 const WebURLResponse& response,
557 const scoped_refptr<UrlData>& url_data) {
558 int64_t first_byte_position, last_byte_position, instance_size;
559 if (!ParseContentRange(response.HttpHeaderField("Content-Range").Utf8(),
560 &first_byte_position, &last_byte_position,
561 &instance_size)) {
562 return false;
563 }
564
565 if (url_data_->length() == kPositionNotSpecified) {
566 url_data->set_length(instance_size);
567 }
568
569 if (first_byte_position > byte_pos()) {
570 return false;
571 }
572 if (last_byte_position + 1 < byte_pos()) {
573 return false;
574 }
575 bytes_to_discard_ = byte_pos() - first_byte_position;
576
577 return true;
578 }
579
580 } // namespace media
581