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