OLD | NEW |
(Empty) | |
| 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 "base/bind.h" |
| 8 #include "base/bits.h" |
| 9 #include "base/callback_helpers.h" |
| 10 #include "base/message_loop/message_loop.h" |
| 11 #include "base/metrics/histogram.h" |
| 12 #include "base/strings/string_number_conversions.h" |
| 13 #include "media/blink/active_loader.h" |
| 14 #include "media/blink/cache_util.h" |
| 15 #include "media/blink/media_blink_export.h" |
| 16 #include "media/blink/url_index.h" |
| 17 #include "net/http/http_byte_range.h" |
| 18 #include "net/http/http_request_headers.h" |
| 19 #include "third_party/WebKit/public/platform/WebURLError.h" |
| 20 #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| 21 |
| 22 using blink::WebFrame; |
| 23 using blink::WebString; |
| 24 using blink::WebURLError; |
| 25 using blink::WebURLLoader; |
| 26 using blink::WebURLLoaderOptions; |
| 27 using blink::WebURLRequest; |
| 28 using blink::WebURLResponse; |
| 29 |
| 30 namespace media { |
| 31 |
| 32 // The number of milliseconds to wait before retrying a failed load. |
| 33 const int kLoaderFailedRetryDelayMs = 250; |
| 34 |
| 35 const int kHttpOK = 200; |
| 36 const int kHttpPartialContent = 206; |
| 37 const int kHttpRangeNotSatisfiable = 416; |
| 38 const int kMaxRetries = 3; |
| 39 |
| 40 ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider( |
| 41 UrlData* url_data, |
| 42 MultiBufferBlockId pos) |
| 43 : pos_(pos), |
| 44 url_data_(url_data), |
| 45 retries_(0), |
| 46 cors_mode_(url_data->cors_mode()), |
| 47 origin_(url_data->url().GetOrigin()), |
| 48 weak_factory_(this) { |
| 49 DCHECK(url_data_) << " pos = " << pos; |
| 50 DCHECK_GE(pos, 0); |
| 51 } |
| 52 |
| 53 void ResourceMultiBufferDataProvider::Start() { |
| 54 // In the case of a re-start, throw away any half-finished blocks. |
| 55 fifo_.clear(); |
| 56 // Prepare the request. |
| 57 WebURLRequest request(url_data_->url()); |
| 58 // TODO(mkwst): Split this into video/audio. |
| 59 request.setRequestContext(WebURLRequest::RequestContextVideo); |
| 60 |
| 61 request.setHTTPHeaderField( |
| 62 WebString::fromUTF8(net::HttpRequestHeaders::kRange), |
| 63 WebString::fromUTF8( |
| 64 net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue())); |
| 65 |
| 66 url_data_->frame()->setReferrerForRequest(request, blink::WebURL()); |
| 67 |
| 68 // Disable compression, compression for audio/video doesn't make sense... |
| 69 request.setHTTPHeaderField( |
| 70 WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding), |
| 71 WebString::fromUTF8("identity;q=1, *;q=0")); |
| 72 |
| 73 // Check for our test WebURLLoader. |
| 74 scoped_ptr<WebURLLoader> loader; |
| 75 if (test_loader_) { |
| 76 loader = test_loader_.Pass(); |
| 77 } else { |
| 78 WebURLLoaderOptions options; |
| 79 if (url_data_->cors_mode() == UrlData::CORS_UNSPECIFIED) { |
| 80 options.allowCredentials = true; |
| 81 options.crossOriginRequestPolicy = |
| 82 WebURLLoaderOptions::CrossOriginRequestPolicyAllow; |
| 83 } else { |
| 84 options.exposeAllResponseHeaders = true; |
| 85 // The author header set is empty, no preflight should go ahead. |
| 86 options.preflightPolicy = WebURLLoaderOptions::PreventPreflight; |
| 87 options.crossOriginRequestPolicy = |
| 88 WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; |
| 89 if (url_data_->cors_mode() == UrlData::CORS_USE_CREDENTIALS) |
| 90 options.allowCredentials = true; |
| 91 } |
| 92 loader.reset(url_data_->frame()->createAssociatedURLLoader(options)); |
| 93 } |
| 94 |
| 95 // Start the resource loading. |
| 96 loader->loadAsynchronously(request, this); |
| 97 active_loader_.reset(new ActiveLoader(loader.Pass())); |
| 98 } |
| 99 |
| 100 ResourceMultiBufferDataProvider::~ResourceMultiBufferDataProvider() {} |
| 101 |
| 102 ///////////////////////////////////////////////////////////////////////////// |
| 103 // MultiBuffer::DataProvider implementation. |
| 104 MultiBufferBlockId ResourceMultiBufferDataProvider::Tell() const { |
| 105 return pos_; |
| 106 } |
| 107 |
| 108 bool ResourceMultiBufferDataProvider::Available() const { |
| 109 if (fifo_.empty()) |
| 110 return false; |
| 111 if (fifo_.back()->end_of_stream()) |
| 112 return true; |
| 113 if (fifo_.front()->data_size() == block_size()) |
| 114 return true; |
| 115 return false; |
| 116 } |
| 117 |
| 118 scoped_refptr<DataBuffer> ResourceMultiBufferDataProvider::Read() { |
| 119 DCHECK(Available()); |
| 120 scoped_refptr<DataBuffer> ret = fifo_.front(); |
| 121 fifo_.pop_front(); |
| 122 ++pos_; |
| 123 return ret; |
| 124 } |
| 125 |
| 126 void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) { |
| 127 if (!active_loader_ || active_loader_->deferred() == deferred) |
| 128 return; |
| 129 active_loader_->SetDeferred(deferred); |
| 130 } |
| 131 |
| 132 ///////////////////////////////////////////////////////////////////////////// |
| 133 // WebURLLoaderClient implementation. |
| 134 |
| 135 void ResourceMultiBufferDataProvider::willFollowRedirect( |
| 136 WebURLLoader* loader, |
| 137 WebURLRequest& newRequest, |
| 138 const WebURLResponse& redirectResponse) { |
| 139 redirects_to_ = newRequest.url(); |
| 140 url_data_->set_valid_until(base::Time::Now() + |
| 141 GetCacheValidUntil(redirectResponse)); |
| 142 |
| 143 // This test is vital for security! |
| 144 if (cors_mode_ == UrlData::CORS_UNSPECIFIED) { |
| 145 if (origin_ != redirects_to_.GetOrigin()) { |
| 146 url_data_->Fail(); |
| 147 } |
| 148 } |
| 149 } |
| 150 |
| 151 void ResourceMultiBufferDataProvider::didSendData( |
| 152 WebURLLoader* loader, |
| 153 unsigned long long bytes_sent, |
| 154 unsigned long long total_bytes_to_be_sent) { |
| 155 NOTIMPLEMENTED(); |
| 156 } |
| 157 |
| 158 void ResourceMultiBufferDataProvider::didReceiveResponse( |
| 159 WebURLLoader* loader, |
| 160 const WebURLResponse& response) { |
| 161 #if ENABLE_DLOG |
| 162 string version; |
| 163 switch (response.httpVersion()) { |
| 164 case WebURLResponse::HTTPVersion_0_9: |
| 165 version = "0.9"; |
| 166 break; |
| 167 case WebURLResponse::HTTPVersion_1_0: |
| 168 version = "1.0"; |
| 169 break; |
| 170 case WebURLResponse::HTTPVersion_1_1: |
| 171 version = "1.1"; |
| 172 break; |
| 173 case WebURLResponse::HTTPVersion_2_0: |
| 174 version = "2.1"; |
| 175 break; |
| 176 } |
| 177 DVLOG(1) << "didReceiveResponse: HTTP/" << version << " " |
| 178 << response.httpStatusCode(); |
| 179 #endif |
| 180 DCHECK(active_loader_); |
| 181 |
| 182 scoped_refptr<UrlData> destination_url_data(url_data_); |
| 183 |
| 184 UrlIndex* url_index = url_data_->url_index(); |
| 185 |
| 186 if (!redirects_to_.is_empty()) { |
| 187 if (!url_index) { |
| 188 // We've been disconnected from the url index. |
| 189 // That means the url_index_ has been destroyed, which means we do not |
| 190 // need to do anything clever. |
| 191 return; |
| 192 } |
| 193 destination_url_data = url_index->GetByUrl(redirects_to_, cors_mode_); |
| 194 redirects_to_ = GURL(); |
| 195 } |
| 196 |
| 197 base::Time last_modified; |
| 198 if (base::Time::FromString( |
| 199 response.httpHeaderField("Last-Modified").utf8().data(), |
| 200 &last_modified)) { |
| 201 destination_url_data->set_last_modified(last_modified); |
| 202 } |
| 203 |
| 204 destination_url_data->set_valid_until(base::Time::Now() + |
| 205 GetCacheValidUntil(response)); |
| 206 |
| 207 uint32 reasons = GetReasonsForUncacheability(response); |
| 208 destination_url_data->set_cacheable(reasons == 0); |
| 209 UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0); |
| 210 int shift = 0; |
| 211 int max_enum = base::bits::Log2Ceiling(kMaxReason); |
| 212 while (reasons) { |
| 213 DCHECK_LT(shift, max_enum); // Sanity check. |
| 214 if (reasons & 0x1) { |
| 215 UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift, |
| 216 max_enum); // PRESUBMIT_IGNORE_UMA_MAX |
| 217 } |
| 218 |
| 219 reasons >>= 1; |
| 220 ++shift; |
| 221 } |
| 222 |
| 223 // Expected content length can be |kPositionNotSpecified|, in that case |
| 224 // |content_length_| is not specified and this is a streaming response. |
| 225 int64 content_length = response.expectedContentLength(); |
| 226 |
| 227 // We make a strong assumption that when we reach here we have either |
| 228 // received a response from HTTP/HTTPS protocol or the request was |
| 229 // successful (in particular range request). So we only verify the partial |
| 230 // response for HTTP and HTTPS protocol. |
| 231 if (destination_url_data->url().SchemeIsHTTPOrHTTPS()) { |
| 232 bool partial_response = (response.httpStatusCode() == kHttpPartialContent); |
| 233 bool ok_response = (response.httpStatusCode() == kHttpOK); |
| 234 |
| 235 // Check to see whether the server supports byte ranges. |
| 236 std::string accept_ranges = |
| 237 response.httpHeaderField("Accept-Ranges").utf8(); |
| 238 if (accept_ranges.find("bytes") != std::string::npos) |
| 239 destination_url_data->set_range_supported(); |
| 240 |
| 241 // If we have verified the partial response and it is correct. |
| 242 // It's also possible for a server to support range requests |
| 243 // without advertising "Accept-Ranges: bytes". |
| 244 if (partial_response && VerifyPartialResponse(response)) { |
| 245 destination_url_data->set_range_supported(); |
| 246 } else if (ok_response && pos_ == 0) { |
| 247 // We accept a 200 response for a Range:0- request, trusting the |
| 248 // Accept-Ranges header, because Apache thinks that's a reasonable thing |
| 249 // to return. |
| 250 destination_url_data->set_length(content_length); |
| 251 } else if (response.httpStatusCode() == kHttpRangeNotSatisfiable) { |
| 252 // Really, we should never request a range that doesn't exist, but |
| 253 // if we do, let's handle it in a sane way. |
| 254 // Unsatisfiable range |
| 255 fifo_.push_back(DataBuffer::CreateEOSBuffer()); |
| 256 destination_url_data->multibuffer()->OnDataProviderEvent(this); |
| 257 return; |
| 258 } else { |
| 259 destination_url_data->Fail(); |
| 260 return; |
| 261 } |
| 262 } else { |
| 263 destination_url_data->set_range_supported(); |
| 264 if (content_length != kPositionNotSpecified) { |
| 265 destination_url_data->set_length(content_length + byte_pos()); |
| 266 } |
| 267 } |
| 268 |
| 269 if (url_index) { |
| 270 destination_url_data = url_index->TryInsert(destination_url_data); |
| 271 } |
| 272 |
| 273 if (destination_url_data != url_data_) { |
| 274 // At this point, we've encountered a redirect, or found a better url data |
| 275 // instance for the data that we're about to download. |
| 276 |
| 277 // First, let's take a ref on the current url data. |
| 278 scoped_refptr<UrlData> old_url_data(url_data_); |
| 279 destination_url_data->Use(); |
| 280 |
| 281 // Take ownership of ourselves. (From the multibuffer) |
| 282 scoped_ptr<DataProvider> self( |
| 283 url_data_->multibuffer()->RemoveProvider(this)); |
| 284 url_data_ = destination_url_data.get(); |
| 285 // Give the ownership to our new owner. |
| 286 url_data_->multibuffer()->AddProvider(self.Pass()); |
| 287 |
| 288 // Call callback to let upstream users know about the transfer. |
| 289 // This will merge the data from the two multibuffers and |
| 290 // cause clients to start using the new UrlData. |
| 291 old_url_data->RedirectTo(destination_url_data); |
| 292 } |
| 293 } |
| 294 |
| 295 void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader, |
| 296 const char* data, |
| 297 int data_length, |
| 298 int encoded_data_length) { |
| 299 DVLOG(1) << "didReceiveData: " << data_length << " bytes"; |
| 300 DCHECK(!Available()); |
| 301 DCHECK(active_loader_); |
| 302 DCHECK_GT(data_length, 0); |
| 303 |
| 304 // When we receive data, we allow more retries. |
| 305 retries_ = 0; |
| 306 |
| 307 while (data_length) { |
| 308 if (fifo_.empty() || fifo_.back()->data_size() == block_size()) { |
| 309 fifo_.push_back(new DataBuffer(block_size())); |
| 310 fifo_.back()->set_data_size(0); |
| 311 } |
| 312 int last_block_size = fifo_.back()->data_size(); |
| 313 int to_append = std::min<int>(data_length, block_size() - last_block_size); |
| 314 DCHECK_GT(to_append, 0); |
| 315 memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append); |
| 316 data += to_append; |
| 317 fifo_.back()->set_data_size(last_block_size + to_append); |
| 318 data_length -= to_append; |
| 319 } |
| 320 |
| 321 if (Available()) |
| 322 url_data_->multibuffer()->OnDataProviderEvent(this); |
| 323 |
| 324 // Beware, this object might be deleted here. |
| 325 } |
| 326 |
| 327 void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader, |
| 328 int dataLength, |
| 329 int encoded_data_length) { |
| 330 NOTIMPLEMENTED(); |
| 331 } |
| 332 |
| 333 void ResourceMultiBufferDataProvider::didReceiveCachedMetadata( |
| 334 WebURLLoader* loader, |
| 335 const char* data, |
| 336 int data_length) { |
| 337 NOTIMPLEMENTED(); |
| 338 } |
| 339 |
| 340 void ResourceMultiBufferDataProvider::didFinishLoading( |
| 341 WebURLLoader* loader, |
| 342 double finishTime, |
| 343 int64_t total_encoded_data_length) { |
| 344 DVLOG(1) << "didFinishLoading"; |
| 345 DCHECK(active_loader_.get()); |
| 346 DCHECK(!Available()); |
| 347 |
| 348 // We're done with the loader. |
| 349 active_loader_.reset(); |
| 350 |
| 351 // If we didn't know the |instance_size_| we do now. |
| 352 int64_t size = byte_pos(); |
| 353 if (!fifo_.empty()) |
| 354 size += fifo_.back()->data_size(); |
| 355 |
| 356 // This request reports something smaller than what we've seen in the past, |
| 357 // Maybe it's transient error? |
| 358 if (url_data_->length() != kPositionNotSpecified && |
| 359 size < url_data_->length()) { |
| 360 if (retries_ < kMaxRetries) { |
| 361 fifo_.clear(); |
| 362 retries_++; |
| 363 base::MessageLoop::current()->PostDelayedTask( |
| 364 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start, |
| 365 weak_factory_.GetWeakPtr()), |
| 366 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs)); |
| 367 return; |
| 368 } else { |
| 369 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass(); |
| 370 url_data_->Fail(); |
| 371 return; |
| 372 } |
| 373 } |
| 374 |
| 375 url_data_->set_length(size); |
| 376 fifo_.push_back(DataBuffer::CreateEOSBuffer()); |
| 377 |
| 378 DCHECK(Available()); |
| 379 url_data_->multibuffer()->OnDataProviderEvent(this); |
| 380 |
| 381 // Beware, this object might be deleted here. |
| 382 } |
| 383 |
| 384 void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader, |
| 385 const WebURLError& error) { |
| 386 DVLOG(1) << "didFail: reason=" << error.reason |
| 387 << ", isCancellation=" << error.isCancellation |
| 388 << ", domain=" << error.domain.utf8().data() |
| 389 << ", localizedDescription=" |
| 390 << error.localizedDescription.utf8().data(); |
| 391 DCHECK(active_loader_.get()); |
| 392 |
| 393 if (retries_ < kMaxRetries) { |
| 394 retries_++; |
| 395 base::MessageLoop::current()->PostDelayedTask( |
| 396 FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start, |
| 397 weak_factory_.GetWeakPtr()), |
| 398 base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs)); |
| 399 } else { |
| 400 // We don't need to continue loading after failure. |
| 401 // |
| 402 // Keep it alive until we exit this method so that |error| remains valid. |
| 403 scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass(); |
| 404 url_data_->Fail(); |
| 405 } |
| 406 } |
| 407 |
| 408 bool ResourceMultiBufferDataProvider::ParseContentRange( |
| 409 const std::string& content_range_str, |
| 410 int64* first_byte_position, |
| 411 int64* last_byte_position, |
| 412 int64* instance_size) { |
| 413 const std::string kUpThroughBytesUnit = "bytes "; |
| 414 if (content_range_str.find(kUpThroughBytesUnit) != 0) |
| 415 return false; |
| 416 std::string range_spec = |
| 417 content_range_str.substr(kUpThroughBytesUnit.length()); |
| 418 size_t dash_offset = range_spec.find("-"); |
| 419 size_t slash_offset = range_spec.find("/"); |
| 420 |
| 421 if (dash_offset == std::string::npos || slash_offset == std::string::npos || |
| 422 slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) { |
| 423 return false; |
| 424 } |
| 425 if (!base::StringToInt64(range_spec.substr(0, dash_offset), |
| 426 first_byte_position) || |
| 427 !base::StringToInt64( |
| 428 range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1), |
| 429 last_byte_position)) { |
| 430 return false; |
| 431 } |
| 432 if (slash_offset == range_spec.length() - 2 && |
| 433 range_spec[slash_offset + 1] == '*') { |
| 434 *instance_size = kPositionNotSpecified; |
| 435 } else { |
| 436 if (!base::StringToInt64(range_spec.substr(slash_offset + 1), |
| 437 instance_size)) { |
| 438 return false; |
| 439 } |
| 440 } |
| 441 if (*last_byte_position < *first_byte_position || |
| 442 (*instance_size != kPositionNotSpecified && |
| 443 *last_byte_position >= *instance_size)) { |
| 444 return false; |
| 445 } |
| 446 |
| 447 return true; |
| 448 } |
| 449 |
| 450 int64_t ResourceMultiBufferDataProvider::byte_pos() const { |
| 451 int64_t ret = pos_; |
| 452 return ret << url_data_->multibuffer()->block_size_shift(); |
| 453 } |
| 454 |
| 455 int64_t ResourceMultiBufferDataProvider::block_size() const { |
| 456 int64_t ret = 1; |
| 457 return ret << url_data_->multibuffer()->block_size_shift(); |
| 458 } |
| 459 |
| 460 bool ResourceMultiBufferDataProvider::VerifyPartialResponse( |
| 461 const WebURLResponse& response) { |
| 462 int64 first_byte_position, last_byte_position, instance_size; |
| 463 if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(), |
| 464 &first_byte_position, &last_byte_position, |
| 465 &instance_size)) { |
| 466 return false; |
| 467 } |
| 468 |
| 469 if (url_data_->length() == kPositionNotSpecified) { |
| 470 url_data_->set_length(instance_size); |
| 471 } |
| 472 |
| 473 if (byte_pos() != first_byte_position) { |
| 474 return false; |
| 475 } |
| 476 |
| 477 return true; |
| 478 } |
| 479 |
| 480 } // namespace media |
OLD | NEW |