Bug 1443942 - Ensure MediaCacheStreams are initialized with the length of the resource, not the length of the byte range response. r?jya draft
authorChris Pearce <cpearce@mozilla.com>
Wed, 04 Apr 2018 12:36:00 +1200
changeset 779068 1e9c6921514f8bf4166607f524d65ae27c0e0f0f
parent 779067 cb197e12965744ebb02db97f7380f42a764a4e93
child 779069 25c4cb99882558fe2d231a77baf9259c6c929d41
push id105647
push userbmo:cpearce@mozilla.com
push dateSun, 08 Apr 2018 23:13:55 +0000
reviewersjya
bugs1443942
milestone61.0a1
Bug 1443942 - Ensure MediaCacheStreams are initialized with the length of the resource, not the length of the byte range response. r?jya I'm seeing intermittent failures of test_midflight_redirect_blocked. In this test, our custom server responds to Firefox's 0- HTTP Byte Range request with a [0,200] response. When Firefox requests 200-, the server responds with a cross origin redirect, and then the remainder of the resource. However sometimes while running test_midflight_redirect_blocked the MP4 demuxer reads through all 200 bytes while trying to parse metadata before the redirect has occurred and fed more data into the cache, and so the demuxer thinks it's hit end of stream, and reports a failure. The demuxer thinks it's hit end of stream, because we initialize the MediaCacheStream length in ChannelMediaResource::Open() with the value of the Content-Length HTTP header. But in an HTTP byte range response, the Content-Length header tells you the length of the range returned, not the length of the entire resource. The length of the resource is in the Content-Range header, we need to use that if available. So to fix this intermittent test failure, we need to also parse the Content-Range header in ChannelMediaResource::Open(), and use the length from that if available. MozReview-Commit-ID: 29pPRsUvxag
dom/media/ChannelMediaResource.cpp
dom/media/ChannelMediaResource.h
--- a/dom/media/ChannelMediaResource.cpp
+++ b/dom/media/ChannelMediaResource.cpp
@@ -317,17 +317,17 @@ ChannelMediaResource::IsTransportSeekabl
   MOZ_ASSERT(NS_IsMainThread());
   return mIsTransportSeekable;
 }
 
 nsresult
 ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
                                               int64_t& aRangeStart,
                                               int64_t& aRangeEnd,
-                                              int64_t& aRangeTotal)
+                                              int64_t& aRangeTotal) const
 {
   NS_ENSURE_ARG(aHttpChan);
 
   nsAutoCString rangeStr;
   nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
                                              rangeStr);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
@@ -446,40 +446,82 @@ ChannelMediaResource::OnDataAvailable(ui
       return rv;
     NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
     count -= read;
   }
 
   return NS_OK;
 }
 
+int64_t
+ChannelMediaResource::CalculateStreamLength() const
+{
+  if (!mChannel) {
+    return -1;
+  }
+
+  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
+  if (!hc) {
+    return -1;
+  }
+
+  bool succeeded = false;
+  Unused << hc->GetRequestSucceeded(&succeeded);
+  if (!succeeded) {
+    return -1;
+  }
+
+  // We can't determine the length of uncompressed payload.
+  const bool isCompressed = IsPayloadCompressed(hc);
+  if (isCompressed) {
+    return -1;
+  }
+
+  int64_t contentLength = -1;
+  if (NS_FAILED(hc->GetContentLength(&contentLength))) {
+    return -1;
+  }
+
+  uint32_t responseStatus = 0;
+  Unused << hc->GetResponseStatus(&responseStatus);
+  if (responseStatus != HTTP_PARTIAL_RESPONSE_CODE) {
+    return contentLength;
+  }
+
+  // We have an HTTP Byte Range response. The Content-Length is the length
+  // of the response, not the resource. We need to parse the Content-Range
+  // header and extract the range total in order to get the stream length.
+  int64_t rangeStart = 0;
+  int64_t rangeEnd = 0;
+  int64_t rangeTotal = 0;
+  bool gotRangeHeader = NS_SUCCEEDED(
+    ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal));
+  if (gotRangeHeader && rangeTotal != -1) {
+    contentLength = std::max(contentLength, rangeTotal);
+  }
+  return contentLength;
+}
+
 nsresult
 ChannelMediaResource::Open(nsIStreamListener** aStreamListener)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   MOZ_ASSERT(aStreamListener);
   MOZ_ASSERT(mChannel);
 
-  int64_t cl = -1;
-  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
-  if (hc && !IsPayloadCompressed(hc)) {
-    if (NS_FAILED(hc->GetContentLength(&cl))) {
-      cl = -1;
-    }
-  }
-
-  nsresult rv = mCacheStream.Init(cl);
+  int64_t streamLength = CalculateStreamLength();
+  nsresult rv = mCacheStream.Init(streamLength);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mSharedInfo = new SharedInfo;
   mSharedInfo->mResources.AppendElement(this);
 
-  mIsLiveStream = cl < 0;
+  mIsLiveStream = streamLength < 0;
   mListener = new Listener(this, 0, ++mLoadID);
   *aStreamListener = mListener;
   NS_ADDREF(*aStreamListener);
   return NS_OK;
 }
 
 nsresult
 ChannelMediaResource::OpenChannel(int64_t aOffset)
--- a/dom/media/ChannelMediaResource.h
+++ b/dom/media/ChannelMediaResource.h
@@ -226,17 +226,21 @@ protected:
   void UpdatePrincipal();
 
   // Parses 'Content-Range' header and returns results via parameters.
   // Returns error if header is not available, values are not parse-able or
   // values are out of range.
   nsresult ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
                                    int64_t& aRangeStart,
                                    int64_t& aRangeEnd,
-                                   int64_t& aRangeTotal);
+                                   int64_t& aRangeTotal) const;
+
+  // Calculates the length of the resource using HTTP headers, if this
+  // is an HTTP channel. Returns -1 on failure, or for non HTTP channels.
+  int64_t CalculateStreamLength() const;
 
   struct Closure
   {
     uint32_t mLoadID;
     ChannelMediaResource* mResource;
   };
 
   static nsresult CopySegmentToCache(nsIInputStream* aInStream,