Bug 1443942 - Ensure MediaCacheStreams are initialized with the length of the resource, not the length of the byte range response. r=jya
authorChris Pearce <cpearce@mozilla.com>
Wed, 04 Apr 2018 12:36:00 +1200
changeset 779717 f2306039790583a2c1f75888010d044600d75f82
parent 779716 79bd527dd8c86844f14a0c2306223f948f5cfcc0
child 779718 8b3f66ef0cc427ba2afca030b31194fb5b7aa95b
push id105836
push userbmo:ato@sny.no
push dateTue, 10 Apr 2018 12:07:22 +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,