Custom protocol loading through AVFoundation does not support byte-range requests.
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 28 Jan 2016 19:17:17 +0000 (19:17 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 28 Jan 2016 19:17:17 +0000 (19:17 +0000)
https://bugs.webkit.org/show_bug.cgi?id=152919
<rdar://problem/23664657>

Reviewed by Alex Christensen.

Source/WebCore:

Tests: http/tests/xmlhttprequest/blob-request-byte-range.html
       TestWebkitAPI/Tests/WebCore/ParsedContentRange.cpp

When loading data through the AVAssetResourceLoaderDelegateProtocol, AVFoundation will issue
requests for specific byte-ranges by adding a "Range:" HTTP header to the NSURLRequest it
passes to the delegate.  WebCore ignores this header, loads the entire resource, and replies
to the callback with the requested subset of the entire resource.

For byte-range requests near the end of a resource, this is inefficient, as the entire
resource up to, and including, the requested range must be loaded before any data can be
returned. Explicitly handle byte-range requests by creating a CachedResourceRequest with the
underlying NSURLRequest (which includes the "Range:" header) rather than just the request's
URL. BlobResourceHandle must be modified to add the "Content-Range:" response header to the
ResourceResponse.

To facilitate both generating and parsing the "Content-Range:" header, add a new
ParsedContentRange class for use by ResourceResponse and its clients. This class provides
methods both for parsing a "Content-Range" header value string, and for generating the
header value from elemental values.

* platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm:
(WebCore::WebCoreAVFResourceLoader::startLoading):
(WebCore::WebCoreAVFResourceLoader::responseReceived):
(WebCore::WebCoreAVFResourceLoader::fulfillRequestWithResource):
* platform/network/BlobResourceHandle.cpp:
(WebCore::BlobResourceHandle::BlobResourceHandle):
(WebCore::BlobResourceHandle::didGetSize):
(WebCore::BlobResourceHandle::seek):
(WebCore::BlobResourceHandle::notifyResponseOnSuccess):
* platform/network/BlobResourceHandle.h:
* platform/network/HTTPHeaderNames.in:
* platform/network/ParsedContentRange.cpp: Added.
(WebCore::areContentRangeValuesValid):
(WebCore::parseContentRange):
(WebCore::ParsedContentRange::ParsedContentRange):
(WebCore::ParsedContentRange::headerValue):
* platform/network/ParsedContentRange.h: Added.
(WebCore::ParsedContentRange::ParsedContentRange):
(WebCore::ParsedContentRange::isValid):
(WebCore::ParsedContentRange::firstBytePosition):
(WebCore::ParsedContentRange::lastBytePosition):
(WebCore::ParsedContentRange::instanceLength):
* platform/network/ResourceResponseBase.cpp:
(WebCore::ResourceResponseBase::updateHeaderParsedState):
(WebCore::parseContentRangeInHeader):
(WebCore::ResourceResponseBase::contentRange):
* platform/network/ResourceResponseBase.h:
* CMakeLists.txt:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* WebCore.xcodeproj/project.pbxproj:

Tools:

Add tests for new ParsedContntRange class.

* TestWebKitAPI/PlatformWin.cmake:
* TestWebKitAPI/TestWebKitAPI.vcxproj/TestWebKitAPI.vcxproj:
* TestWebKitAPI/TestWebKitAPI.vcxproj/TestWebKitAPI.vcxproj.filters:
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebCore/ParsedContentRange.cpp: Added.
(TestWebKitAPI::TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@195764 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
LayoutTests/http/tests/xmlhttprequest/blob-request-byte-range-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/blob-request-byte-range.html [new file with mode: 0644]
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm
Source/WebCore/platform/network/BlobResourceHandle.cpp
Source/WebCore/platform/network/BlobResourceHandle.h
Source/WebCore/platform/network/HTTPHeaderNames.in
Source/WebCore/platform/network/ParsedContentRange.cpp [new file with mode: 0644]
Source/WebCore/platform/network/ParsedContentRange.h [new file with mode: 0644]
Source/WebCore/platform/network/ResourceResponseBase.cpp
Source/WebCore/platform/network/ResourceResponseBase.h
Tools/ChangeLog
Tools/TestWebKitAPI/PlatformWin.cmake
Tools/TestWebKitAPI/TestWebKitAPI.vcxproj/TestWebKitAPI.vcxproj
Tools/TestWebKitAPI/TestWebKitAPI.vcxproj/TestWebKitAPI.vcxproj.filters
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebCore/ParsedContentRange.cpp [new file with mode: 0644]

diff --git a/LayoutTests/http/tests/xmlhttprequest/blob-request-byte-range-expected.txt b/LayoutTests/http/tests/xmlhttprequest/blob-request-byte-range-expected.txt
new file mode 100644 (file)
index 0000000..ece9296
--- /dev/null
@@ -0,0 +1,5 @@
+Test an XMLHttpRequest of a Blob URL requesting a byte-range responds appropriately.
+
+PASS: "req.status" == "206"
+PASS: "req.getResponseHeader("Content-Range")" == "bytes 1-2/4"
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/blob-request-byte-range.html b/LayoutTests/http/tests/xmlhttprequest/blob-request-byte-range.html
new file mode 100644 (file)
index 0000000..3f0a354
--- /dev/null
@@ -0,0 +1,45 @@
+<html>
+<body>
+<p>Test an XMLHttpRequest of a Blob URL requesting a byte-range responds appropriately.</p>
+<pre id="console"></pre>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+function log(text)
+{
+    var console = document.getElementById('console');
+    console.appendChild(document.createTextNode(text + '\n'));
+}
+
+function test(expect, actual)
+{
+    var result = eval(actual);
+    if (expect == result)
+        log(`PASS: "${ actual }" == "${ expect }"`);
+    else
+        log(`FAIL: "${ actual }" EXPECT "${ expect }" GOT "${ result }"`);
+}
+
+var array = new Int8Array([0, 1, 2, 3]);
+var blob = new Blob(array);
+var url = URL.createObjectURL(blob);
+
+var req = new XMLHttpRequest;
+req.responseType = 'blob';
+req.open('GET', url);
+req.setRequestHeader('Range', 'bytes=1-2');
+req.onreadystatechange = function() {
+    if (req.readyState == 4) {
+        test(206, 'req.status');
+        test('bytes 1-2/4', 'req.getResponseHeader("Content-Range")');
+        if (window.testRunner)
+            testRunner.notifyDone();
+    }
+};
+req.send();
+
+</script>
+</body>
index 559c1ba..aabded2 100644 (file)
@@ -2331,6 +2331,7 @@ set(WebCore_SOURCES
     platform/network/HTTPParsers.cpp
     platform/network/MIMEHeader.cpp
     platform/network/NetworkStateNotifier.cpp
+    platform/network/ParsedContentRange.cpp
     platform/network/ParsedContentType.cpp
     platform/network/ProtectionSpaceBase.cpp
     platform/network/ProxyServer.cpp
index c34e20f..6470261 100644 (file)
@@ -1,3 +1,63 @@
+2016-01-08  Jer Noble  <jer.noble@apple.com>
+
+        Custom protocol loading through AVFoundation does not support byte-range requests.
+        https://bugs.webkit.org/show_bug.cgi?id=152919
+        <rdar://problem/23664657>
+
+        Reviewed by Alex Christensen.
+
+        Tests: http/tests/xmlhttprequest/blob-request-byte-range.html
+               TestWebkitAPI/Tests/WebCore/ParsedContentRange.cpp
+
+        When loading data through the AVAssetResourceLoaderDelegateProtocol, AVFoundation will issue
+        requests for specific byte-ranges by adding a "Range:" HTTP header to the NSURLRequest it
+        passes to the delegate.  WebCore ignores this header, loads the entire resource, and replies
+        to the callback with the requested subset of the entire resource.
+
+        For byte-range requests near the end of a resource, this is inefficient, as the entire
+        resource up to, and including, the requested range must be loaded before any data can be
+        returned. Explicitly handle byte-range requests by creating a CachedResourceRequest with the
+        underlying NSURLRequest (which includes the "Range:" header) rather than just the request's
+        URL. BlobResourceHandle must be modified to add the "Content-Range:" response header to the
+        ResourceResponse. 
+
+        To facilitate both generating and parsing the "Content-Range:" header, add a new
+        ParsedContentRange class for use by ResourceResponse and its clients. This class provides
+        methods both for parsing a "Content-Range" header value string, and for generating the
+        header value from elemental values.
+
+        * platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm:
+        (WebCore::WebCoreAVFResourceLoader::startLoading):
+        (WebCore::WebCoreAVFResourceLoader::responseReceived):
+        (WebCore::WebCoreAVFResourceLoader::fulfillRequestWithResource):
+        * platform/network/BlobResourceHandle.cpp:
+        (WebCore::BlobResourceHandle::BlobResourceHandle):
+        (WebCore::BlobResourceHandle::didGetSize):
+        (WebCore::BlobResourceHandle::seek):
+        (WebCore::BlobResourceHandle::notifyResponseOnSuccess):
+        * platform/network/BlobResourceHandle.h:
+        * platform/network/HTTPHeaderNames.in:
+        * platform/network/ParsedContentRange.cpp: Added.
+        (WebCore::areContentRangeValuesValid):
+        (WebCore::parseContentRange):
+        (WebCore::ParsedContentRange::ParsedContentRange):
+        (WebCore::ParsedContentRange::headerValue):
+        * platform/network/ParsedContentRange.h: Added.
+        (WebCore::ParsedContentRange::ParsedContentRange):
+        (WebCore::ParsedContentRange::isValid):
+        (WebCore::ParsedContentRange::firstBytePosition):
+        (WebCore::ParsedContentRange::lastBytePosition):
+        (WebCore::ParsedContentRange::instanceLength):
+        * platform/network/ResourceResponseBase.cpp:
+        (WebCore::ResourceResponseBase::updateHeaderParsedState):
+        (WebCore::parseContentRangeInHeader):
+        (WebCore::ResourceResponseBase::contentRange):
+        * platform/network/ResourceResponseBase.h:
+        * CMakeLists.txt:
+        * WebCore.vcxproj/WebCore.vcxproj:
+        * WebCore.vcxproj/WebCore.vcxproj.filters:
+        * WebCore.xcodeproj/project.pbxproj:
+
 2016-01-28  Chris Dumez  <cdumez@apple.com>
 
         Storage interface's attributes / operations should be enumerable
index 2447fe4..ab7433f 100644 (file)
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="..\platform\network\ParsedContentRange.cpp" />
     <ClCompile Include="..\platform\network\ParsedContentType.cpp" />
     <ClCompile Include="..\platform\network\ProtectionSpaceBase.cpp" />
     <ClCompile Include="..\platform\network\ProxyServer.cpp" />
     <ClInclude Include="..\platform\network\NetworkingContext.h" />
     <ClInclude Include="..\platform\network\NetworkStateNotifier.h" />
     <ClInclude Include="..\platform\network\NetworkStorageSession.h" />
+    <ClInclude Include="..\platform\network\ParsedContentRange.h" />
     <ClInclude Include="..\platform\network\ParsedContentType.h" />
     <ClInclude Include="..\platform\network\PlatformCookieJar.h" />
     <ClInclude Include="..\platform\network\ProtectionSpace.h" />
index 64d4bf5..d259b27 100644 (file)
     <ClCompile Include="..\platform\network\NetworkStorageSessionStub.cpp">
       <Filter>platform\network</Filter>
     </ClCompile>
+    <ClCompile Include="..\platform\network\ParsedContentRange.cpp">
+      <Filter>platform\network</Filter>
+    </ClCompile>
     <ClCompile Include="..\platform\network\ParsedContentType.cpp">
       <Filter>platform\network</Filter>
     </ClCompile>
     <ClInclude Include="..\platform\network\NetworkStorageSession.h">
       <Filter>platform\network</Filter>
     </ClInclude>
+    <ClInclude Include="..\platform\network\ParsedContenRange.h">
+      <Filter>platform\network</Filter>
+    </ClInclude>
     <ClInclude Include="..\platform\network\ParsedContentType.h">
       <Filter>platform\network</Filter>
     </ClInclude>
index 4ace5cf..84289cf 100644 (file)
                CDC979F51C498C0900DB50D4 /* WebCoreNSErrorExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC979F31C498C0900DB50D4 /* WebCoreNSErrorExtras.h */; };
                CDCA82961679100F00875714 /* TextTrackRepresentationIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDCA82941679100F00875714 /* TextTrackRepresentationIOS.mm */; };
                CDCA98EB18B2C8EB00C12FF9 /* CDMPrivateMediaPlayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDCA98EA18B2C8EB00C12FF9 /* CDMPrivateMediaPlayer.cpp */; };
+               CDCD41E71C3DDB0900965D99 /* ParsedContentRange.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDCD41E51C3DDB0900965D99 /* ParsedContentRange.cpp */; };
+               CDCD41E81C3DDB0A00965D99 /* ParsedContentRange.h in Headers */ = {isa = PBXBuildFile; fileRef = CDCD41E61C3DDB0900965D99 /* ParsedContentRange.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CDCFABBD18C0AF78006F8450 /* SelectionSubtreeRoot.h in Headers */ = {isa = PBXBuildFile; fileRef = CDCFABBB18C0AE31006F8450 /* SelectionSubtreeRoot.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CDCFABBE18C0AF84006F8450 /* SelectionSubtreeRoot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDCFABBC18C0AF19006F8450 /* SelectionSubtreeRoot.cpp */; };
                CDD525D7145B6DD0008D204D /* JSHTMLMediaElementCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDF65CCC145B6AFE00C4C7AA /* JSHTMLMediaElementCustom.cpp */; };
                CDCA82941679100F00875714 /* TextTrackRepresentationIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TextTrackRepresentationIOS.mm; sourceTree = "<group>"; };
                CDCA98E918B2C8D000C12FF9 /* CDMPrivateMediaPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDMPrivateMediaPlayer.h; sourceTree = "<group>"; };
                CDCA98EA18B2C8EB00C12FF9 /* CDMPrivateMediaPlayer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CDMPrivateMediaPlayer.cpp; sourceTree = "<group>"; };
+               CDCD41E51C3DDB0900965D99 /* ParsedContentRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParsedContentRange.cpp; sourceTree = "<group>"; };
+               CDCD41E61C3DDB0900965D99 /* ParsedContentRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParsedContentRange.h; sourceTree = "<group>"; };
                CDCE5CD014633BC900D47CCA /* EventTargetFactory.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EventTargetFactory.in; sourceTree = "<group>"; };
                CDCFABBB18C0AE31006F8450 /* SelectionSubtreeRoot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectionSubtreeRoot.h; sourceTree = "<group>"; };
                CDCFABBC18C0AF19006F8450 /* SelectionSubtreeRoot.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SelectionSubtreeRoot.cpp; sourceTree = "<group>"; };
                                1A7FA61A0DDA3BBE0028F8A5 /* NetworkStateNotifier.cpp */,
                                1A7FA6180DDA3B3A0028F8A5 /* NetworkStateNotifier.h */,
                                E13EF3421684ECF40034C83F /* NetworkStorageSession.h */,
+                               CDCD41E51C3DDB0900965D99 /* ParsedContentRange.cpp */,
+                               CDCD41E61C3DDB0900965D99 /* ParsedContentRange.h */,
                                447958021643B47B001E0A7F /* ParsedContentType.cpp */,
                                447958031643B47B001E0A7F /* ParsedContentType.h */,
                                51B454E91B4DAE7D0085EAA6 /* PingHandle.h */,
                                939885C408B7E3D100E707C4 /* EventNames.h in Headers */,
                                8F67561B1288B17B0047ACA3 /* EventQueue.h in Headers */,
                                E0FEF372B17C53EAC1C1FBEE /* EventSource.h in Headers */,
+                               CDCD41E81C3DDB0A00965D99 /* ParsedContentRange.h in Headers */,
                                E12EDB7B0B308A78002704B6 /* EventTarget.h in Headers */,
                                97AA3CA5145237CC003E1DA6 /* EventTargetHeaders.h in Headers */,
                                97AA3CA6145237CC003E1DA6 /* EventTargetInterfaces.h in Headers */,
                                A454424A119B3661009BE912 /* HTMLMeterElement.cpp in Sources */,
                                A8CFF7A90A156978000A4234 /* HTMLModElement.cpp in Sources */,
                                A8DF3FD5097FA0FC0052981B /* HTMLNameCollection.cpp in Sources */,
+                               CDCD41E71C3DDB0900965D99 /* ParsedContentRange.cpp in Sources */,
                                A8D06B3A0A265DCD005E7203 /* HTMLNames.cpp in Sources */,
                                A871D45B0A127CBC00B12A68 /* HTMLObjectElement.cpp in Sources */,
                                A8EA79FB0A1916DF00A8EF5F /* HTMLOListElement.cpp in Sources */,
index 0384822..8f045f2 100644 (file)
@@ -65,10 +65,10 @@ void WebCoreAVFResourceLoader::startLoading()
     if (m_resource || !m_parent)
         return;
 
-    URL requestURL = [[m_avRequest.get() request] URL];
+    NSURLRequest *nsRequest = [m_avRequest.get() request];
 
     // ContentSecurityPolicyImposition::DoPolicyCheck is a placeholder value. It does not affect the request since Content Security Policy does not apply to raw resources.
-    CachedResourceRequest request(ResourceRequest(requestURL), ResourceLoaderOptions(SendCallbacks, DoNotSniffContent, BufferData, DoNotAllowStoredCredentials, DoNotAskClientForCrossOriginCredentials, ClientDidNotRequestCredentials, DoSecurityCheck, UseDefaultOriginRestrictionsForType, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::DoPolicyCheck, DefersLoadingPolicy::AllowDefersLoading));
+    CachedResourceRequest request(nsRequest, ResourceLoaderOptions(SendCallbacks, DoNotSniffContent, BufferData, DoNotAllowStoredCredentials, DoNotAskClientForCrossOriginCredentials, ClientDidNotRequestCredentials, DoSecurityCheck, UseDefaultOriginRestrictionsForType, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::DoPolicyCheck, DefersLoadingPolicy::AllowDefersLoading));
 
     request.mutableResourceRequest().setPriority(ResourceLoadPriority::Low);
     CachedResourceLoader* loader = m_parent->player()->cachedResourceLoader();
@@ -76,7 +76,7 @@ void WebCoreAVFResourceLoader::startLoading()
     if (m_resource)
         m_resource->addClient(this);
     else {
-        LOG_ERROR("Failed to start load for media at url %s", requestURL.string().ascii().data());
+        LOG_ERROR("Failed to start load for media at url %s", [[[nsRequest URL] absoluteString] UTF8String]);
         [m_avRequest.get() finishLoadingWithError:0];
     }
 }
@@ -101,8 +101,8 @@ void WebCoreAVFResourceLoader::invalidate()
 
 void WebCoreAVFResourceLoader::responseReceived(CachedResource* resource, const ResourceResponse& response)
 {
+    ASSERT(resource);
     ASSERT(resource == m_resource);
-    UNUSED_PARAM(resource);
 
     int status = response.httpStatusCode();
     if (status && (status < 200 || status > 299)) {
@@ -114,7 +114,9 @@ void WebCoreAVFResourceLoader::responseReceived(CachedResource* resource, const
         String uti = UTIFromMIMEType(response.mimeType().createCFString().get()).get();
 
         [contentInfo setContentType:uti];
-        [contentInfo setContentLength:response.expectedContentLength()];
+
+        ParsedContentRange& contentRange = resource->response().contentRange();
+        [contentInfo setContentLength:contentRange.isValid() ? contentRange.instanceLength() : response.expectedContentLength()];
         [contentInfo setByteRangeAccessSupported:YES];
 
         if (![m_avRequest dataRequest]) {
@@ -147,6 +149,7 @@ void WebCoreAVFResourceLoader::notifyFinished(CachedResource* resource)
 
 void WebCoreAVFResourceLoader::fulfillRequestWithResource(CachedResource* resource)
 {
+    ASSERT(resource);
     ASSERT(resource == m_resource);
     AVAssetResourceLoadingDataRequest* dataRequest = [m_avRequest dataRequest];
     if (!dataRequest)
@@ -156,6 +159,11 @@ void WebCoreAVFResourceLoader::fulfillRequestWithResource(CachedResource* resour
     if (!data)
         return;
 
+    NSUInteger responseOffset = 0;
+    ParsedContentRange contentRange = resource->response().contentRange();
+    if (contentRange.isValid())
+        responseOffset = static_cast<NSUInteger>(contentRange.firstBytePosition());
+
     // Check for possible unsigned overflow.
     ASSERT([dataRequest currentOffset] >= [dataRequest requestedOffset]);
     ASSERT([dataRequest requestedLength] >= ([dataRequest currentOffset] - [dataRequest requestedOffset]));
@@ -163,11 +171,11 @@ void WebCoreAVFResourceLoader::fulfillRequestWithResource(CachedResource* resour
     NSUInteger remainingLength = [dataRequest requestedLength] - static_cast<NSUInteger>([dataRequest currentOffset] - [dataRequest requestedOffset]);
     do {
         // Check to see if there is any data available in the buffer to fulfill the data request.
-        if (data->size() <= [dataRequest currentOffset])
+        if (data->size() <= [dataRequest currentOffset] - responseOffset)
             return;
 
         const char* someData;
-        NSUInteger receivedLength = data->getSomeData(someData, static_cast<unsigned>([dataRequest currentOffset]));
+        NSUInteger receivedLength = data->getSomeData(someData, static_cast<unsigned>([dataRequest currentOffset] - responseOffset));
 
         // Create an NSData with only as much of the received data as necessary to fulfill the request.
         NSUInteger length = MIN(receivedLength, remainingLength);
index c06dff7..80d5465 100644 (file)
@@ -38,6 +38,7 @@
 #include "FileSystem.h"
 #include "HTTPHeaderNames.h"
 #include "HTTPParsers.h"
+#include "ParsedContentRange.h"
 #include "URL.h"
 #include "ResourceError.h"
 #include "ResourceHandleClient.h"
@@ -50,7 +51,6 @@
 namespace WebCore {
 
 static const unsigned bufferSize = 512 * 1024;
-static const long long positionNotSpecified = -1;
 
 static const int httpOK = 200;
 static const int httpPartialContent = 206;
@@ -159,16 +159,6 @@ BlobResourceHandle::BlobResourceHandle(BlobData* blobData, const ResourceRequest
     : ResourceHandle(0, request, client, false, false)
     , m_blobData(blobData)
     , m_async(async)
-    , m_errorCode(0)
-    , m_aborted(false)
-    , m_rangeOffset(positionNotSpecified)
-    , m_rangeEnd(positionNotSpecified)
-    , m_rangeSuffixLength(positionNotSpecified)
-    , m_totalRemainingSize(0)
-    , m_currentItemReadSize(0)
-    , m_sizeItemCount(0)
-    , m_readItemCount(0)
-    , m_fileOpened(false)
 {
     if (m_async)
         m_asyncStream = std::make_unique<AsyncFileStream>(*this);
@@ -300,6 +290,7 @@ void BlobResourceHandle::didGetSize(long long size)
     m_itemLengthList.append(size);
 
     // Count the size.
+    m_totalSize += size;
     m_totalRemainingSize += size;
     m_sizeItemCount++;
 
@@ -312,13 +303,13 @@ void BlobResourceHandle::seek()
     ASSERT(isMainThread());
 
     // Convert from the suffix length to the range.
-    if (m_rangeSuffixLength != positionNotSpecified) {
+    if (m_rangeSuffixLength != kPositionNotSpecified) {
         m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
         m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
     }
 
     // Bail out if the range is not provided.
-    if (m_rangeOffset == positionNotSpecified)
+    if (m_rangeOffset == kPositionNotSpecified)
         return;
 
     // Skip the initial items that are not in the range.
@@ -330,7 +321,7 @@ void BlobResourceHandle::seek()
     m_currentItemReadSize = offset;
 
     // Adjust the total remaining size in order not to go beyond the range.
-    if (m_rangeEnd != positionNotSpecified) {
+    if (m_rangeEnd != kPositionNotSpecified) {
         long long rangeSize = m_rangeEnd - m_rangeOffset + 1;
         if (m_totalRemainingSize > rangeSize)
             m_totalRemainingSize = rangeSize;
@@ -583,10 +574,12 @@ void BlobResourceHandle::notifyResponseOnSuccess()
 {
     ASSERT(isMainThread());
 
-    bool isRangeRequest = m_rangeOffset != positionNotSpecified;
+    bool isRangeRequest = m_rangeOffset != kPositionNotSpecified;
     ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String());
     response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK);
     response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText);
+    if (isRangeRequest)
+        response.setHTTPHeaderField(HTTPHeaderName::ContentRange, ParsedContentRange(m_rangeOffset, m_rangeEnd, m_totalSize).headerValue());
     // FIXME: If a resource identified with a blob: URL is a File object, user agents must use that file's name attribute,
     // as if the response had a Content-Disposition header with the filename parameter set to the File's name attribute.
     // Notably, this will affect a name suggested in "File Save As".
index 676dbff..55dcc1e 100644 (file)
@@ -90,22 +90,25 @@ private:
     void notifyFail(int errorCode);
     void notifyFinish();
 
+    enum { kPositionNotSpecified = -1 };
+
     RefPtr<BlobData> m_blobData;
     bool m_async;
     std::unique_ptr<AsyncFileStream> m_asyncStream; // For asynchronous loading.
     std::unique_ptr<FileStream> m_stream; // For synchronous loading.
     Vector<char> m_buffer;
     Vector<long long> m_itemLengthList;
-    int m_errorCode;
-    bool m_aborted;
-    long long m_rangeOffset;
-    long long m_rangeEnd;
-    long long m_rangeSuffixLength;
-    long long m_totalRemainingSize;
-    long long m_currentItemReadSize;
-    unsigned m_sizeItemCount;
-    unsigned m_readItemCount;
-    bool m_fileOpened;
+    int m_errorCode { 0 };
+    bool m_aborted { false };
+    long long m_rangeOffset { kPositionNotSpecified };
+    long long m_rangeEnd { kPositionNotSpecified };
+    long long m_rangeSuffixLength { kPositionNotSpecified };
+    long long m_totalSize { 0 };
+    long long m_totalRemainingSize { 0 };
+    long long m_currentItemReadSize { 0 };
+    unsigned m_sizeItemCount { 0 };
+    unsigned m_readItemCount { 0 };
+    bool m_fileOpened { false };
 };
 
 } // namespace WebCore
index 0e32926..34982ca 100644 (file)
@@ -49,6 +49,7 @@ Content-Security-Policy
 Content-Security-Policy-Report-Only
 Content-Type
 Content-Transfer-Encoding
+Content-Range
 Cookie
 Cookie2
 Date
diff --git a/Source/WebCore/platform/network/ParsedContentRange.cpp b/Source/WebCore/platform/network/ParsedContentRange.cpp
new file mode 100644 (file)
index 0000000..4e46ac4
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#include "config.h"
+#include "ParsedContentRange.h"
+
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+static bool areContentRangeValuesValid(int64_t firstBytePosition, int64_t lastBytePosition, int64_t instanceLength)
+{
+    // From <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>
+    // 14.16 Content-Range
+    // A byte-content-range-spec with a byte-range-resp-spec whose last- byte-pos value is less than its first-byte-pos value,
+    // or whose instance-length value is less than or equal to its last-byte-pos value, is invalid.
+    if (firstBytePosition < 0)
+        return false;
+
+    if (lastBytePosition < firstBytePosition)
+        return false;
+
+    if (instanceLength == ParsedContentRange::UnknownLength)
+        return true;
+
+    return lastBytePosition < instanceLength;
+}
+
+static bool parseContentRange(const String& headerValue, int64_t& firstBytePosition, int64_t& lastBytePosition, int64_t& instanceLength)
+{
+    // From <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html>
+    // 14.16 Content-Range
+    //
+    // Content-Range = "Content-Range" ":" content-range-spec
+    // content-range-spec      = byte-content-range-spec
+    // byte-content-range-spec = bytes-unit SP
+    //                          byte-range-resp-spec "/"
+    //                          ( instance-length | "*" )
+    // byte-range-resp-spec = (first-byte-pos "-" last-byte-pos)
+    //                               | "*"
+    // instance-length           = 1*DIGIT
+
+    static const char* prefix = "bytes ";
+    static const size_t prefixLength = 6;
+
+    if (!headerValue.startsWith(prefix))
+        return false;
+
+    size_t byteSeparatorTokenLoc = headerValue.find('-', prefixLength);
+    if (byteSeparatorTokenLoc == notFound)
+        return false;
+
+    size_t instanceLengthSeparatorToken = headerValue.find('/', byteSeparatorTokenLoc + 1);
+    if (instanceLengthSeparatorToken == notFound)
+        return false;
+
+    bool isOk = true;
+    String firstByteString = headerValue.substring(prefixLength, byteSeparatorTokenLoc - prefixLength);
+    if (!firstByteString.isAllSpecialCharacters<isASCIIDigit>())
+        return false;
+
+    firstBytePosition = firstByteString.toInt64Strict(&isOk);
+    if (!isOk)
+        return false;
+
+    String lastByteString = headerValue.substring(byteSeparatorTokenLoc + 1, instanceLengthSeparatorToken - (byteSeparatorTokenLoc + 1));
+    if (!lastByteString.isAllSpecialCharacters<isASCIIDigit>())
+        return false;
+
+    lastBytePosition = lastByteString.toInt64Strict(&isOk);
+    if (!isOk)
+        return false;
+
+    String instanceString = headerValue.substring(instanceLengthSeparatorToken + 1);
+    if (instanceString == "*")
+        instanceLength = ParsedContentRange::UnknownLength;
+    else {
+        if (!instanceString.isAllSpecialCharacters<isASCIIDigit>())
+            return false;
+
+        instanceLength = instanceString.toInt64Strict(&isOk);
+        if (!isOk)
+            return false;
+    }
+
+    return areContentRangeValuesValid(firstBytePosition, lastBytePosition, instanceLength);
+}
+
+ParsedContentRange::ParsedContentRange(const String& headerValue)
+{
+    m_isValid = parseContentRange(headerValue, m_firstBytePosition, m_lastBytePosition, m_instanceLength);
+}
+
+ParsedContentRange::ParsedContentRange(int64_t firstBytePosition, int64_t lastBytePosition, int64_t instanceLength)
+    : m_firstBytePosition(firstBytePosition)
+    , m_lastBytePosition(lastBytePosition)
+    , m_instanceLength(instanceLength)
+{
+    m_isValid = areContentRangeValuesValid(m_firstBytePosition, m_lastBytePosition, m_instanceLength);
+}
+
+String ParsedContentRange::headerValue() const
+{
+    if (!m_isValid)
+        return String();
+    if (m_instanceLength == UnknownLength)
+        return String::format("bytes %" PRId64 "-%" PRId64 "/*", m_firstBytePosition, m_lastBytePosition);
+    return String::format("bytes %" PRId64 "-%" PRId64 "/%" PRId64, m_firstBytePosition, m_lastBytePosition, m_instanceLength);
+}
+
+}
diff --git a/Source/WebCore/platform/network/ParsedContentRange.h b/Source/WebCore/platform/network/ParsedContentRange.h
new file mode 100644 (file)
index 0000000..cfbf2da
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef ParsedContentRange_h
+#define ParsedContentRange_h
+
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class ParsedContentRange {
+public:
+    WEBCORE_EXPORT explicit ParsedContentRange(const String&);
+    WEBCORE_EXPORT ParsedContentRange() { }
+    WEBCORE_EXPORT ParsedContentRange(int64_t firstBytePosition, int64_t lastBytePosition, int64_t instanceLength);
+
+    WEBCORE_EXPORT bool isValid() const { return m_isValid; }
+    int64_t firstBytePosition() const { return m_firstBytePosition; }
+    int64_t lastBytePosition() const { return m_lastBytePosition; }
+    int64_t instanceLength() const { return m_instanceLength; }
+
+    WEBCORE_EXPORT String headerValue() const;
+
+    enum { UnknownLength = std::numeric_limits<int64_t>::max() };
+
+private:
+    template<typename T> static bool isPositive(T);
+
+    bool m_isValid { false };
+    int64_t m_firstBytePosition { 0 };
+    int64_t m_lastBytePosition { 0 };
+    int64_t m_instanceLength { UnknownLength };
+};
+
+}
+
+#endif
index 92ab24b..d0930dc 100644 (file)
@@ -30,6 +30,7 @@
 #include "CacheValidation.h"
 #include "HTTPHeaderNames.h"
 #include "HTTPParsers.h"
+#include "ParsedContentRange.h"
 #include "ResourceResponse.h"
 #include <wtf/CurrentTime.h>
 #include <wtf/MathExtras.h>
@@ -300,6 +301,10 @@ void ResourceResponseBase::updateHeaderParsedState(HTTPHeaderName name)
         m_haveParsedLastModifiedHeader = false;
         break;
 
+    case HTTPHeaderName::ContentRange:
+        m_haveParsedContentRangeHeader = false;
+        break;
+
     default:
         break;
     }
@@ -461,6 +466,27 @@ Optional<std::chrono::system_clock::time_point> ResourceResponseBase::lastModifi
     return m_lastModified;
 }
 
+static ParsedContentRange parseContentRangeInHeader(const HTTPHeaderMap& headers)
+{
+    String contentRangeValue = headers.get(HTTPHeaderName::ContentRange);
+    if (contentRangeValue.isEmpty())
+        return ParsedContentRange();
+
+    return ParsedContentRange(contentRangeValue);
+}
+
+ParsedContentRange& ResourceResponseBase::contentRange() const
+{
+    lazyInit(CommonFieldsOnly);
+
+    if (!m_haveParsedContentRangeHeader) {
+        m_contentRange = parseContentRangeInHeader(m_httpHeaderFields);
+        m_haveParsedContentRangeHeader = true;
+    }
+
+    return m_contentRange;
+}
+
 bool ResourceResponseBase::isAttachment() const
 {
     lazyInit(AllFields);
index 5e3db39..ec709b3 100644 (file)
@@ -30,6 +30,7 @@
 #include "CacheValidation.h"
 #include "CertificateInfo.h"
 #include "HTTPHeaderMap.h"
+#include "ParsedContentRange.h"
 #include "ResourceLoadTiming.h"
 #include "URL.h"
 
@@ -110,6 +111,7 @@ public:
     WEBCORE_EXPORT Optional<std::chrono::microseconds> age() const;
     WEBCORE_EXPORT Optional<std::chrono::system_clock::time_point> expires() const;
     WEBCORE_EXPORT Optional<std::chrono::system_clock::time_point> lastModified() const;
+    ParsedContentRange& contentRange() const;
 
     // This is primarily for testing support. It is not necessarily accurate in all scenarios.
     enum class Source { Unknown, Network, DiskCache, DiskCacheAfterValidation, MemoryCache, MemoryCacheAfterValidation };
@@ -175,6 +177,7 @@ private:
     mutable Optional<std::chrono::system_clock::time_point> m_date;
     mutable Optional<std::chrono::system_clock::time_point> m_expires;
     mutable Optional<std::chrono::system_clock::time_point> m_lastModified;
+    mutable ParsedContentRange m_contentRange;
     mutable CacheControlDirectives m_cacheControlDirectives;
 
     mutable bool m_haveParsedCacheControlHeader { false };
@@ -182,6 +185,7 @@ private:
     mutable bool m_haveParsedDateHeader { false };
     mutable bool m_haveParsedExpiresHeader { false };
     mutable bool m_haveParsedLastModifiedHeader { false };
+    mutable bool m_haveParsedContentRangeHeader { false };
 
     Source m_source { Source::Unknown };
 };
index 7c7263e..c48add3 100644 (file)
@@ -1,3 +1,20 @@
+2016-01-12  Jer Noble  <jer.noble@apple.com>
+
+        Custom protocol loading through AVFoundation does not support byte-range requests.
+        https://bugs.webkit.org/show_bug.cgi?id=152919
+        <rdar://problem/23664657>
+
+        Reviewed by Alex Christensen.
+
+        Add tests for new ParsedContntRange class.
+
+        * TestWebKitAPI/PlatformWin.cmake:
+        * TestWebKitAPI/TestWebKitAPI.vcxproj/TestWebKitAPI.vcxproj:
+        * TestWebKitAPI/TestWebKitAPI.vcxproj/TestWebKitAPI.vcxproj.filters:
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebCore/ParsedContentRange.cpp: Added.
+        (TestWebKitAPI::TEST):
+
 2016-01-28  Konstantin Tokarev  <annulen@yandex.ru>
 
         Use isAnyWindows() instead of isCygwin() || isWindows() in Perl scripts.
index 39f9807..111ef4c 100644 (file)
@@ -41,6 +41,7 @@ set(TestWebCoreLib_SOURCES
     ${TESTWEBKITAPI_DIR}/Tests/WebCore/CalculationValue.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WebCore/CSSParser.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WebCore/LayoutUnit.cpp
+    ${TESTWEBKITAPI_DIR}/Tests/WebCore/ParsedContentRange.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WebCore/TimeRanges.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WebCore/URL.cpp
 )
index a484edc..87f79f4 100644 (file)
     <ClCompile Include="..\Tests\WebCore\CalculationValue.cpp" />
     <ClCompile Include="..\Tests\WebCore\CSSParser.cpp" />
     <ClCompile Include="..\Tests\WebCore\LayoutUnit.cpp" />
+    <ClCompile Include="..\Tests\WebCore\ParsedContentRange.cpp" />
     <ClCompile Include="..\Tests\WebCore\TimeRanges.cpp" />
     <ClCompile Include="..\Tests\WebCore\URL.cpp" />
     <ClCompile Include="..\Tests\WebCore\win\BitmapImage.cpp">
index a06aed3..8907724 100644 (file)
     <ClCompile Include="..\Tests\WebCore\CalculationValue.cpp">
       <Filter>Tests\WebCore</Filter>
     </ClCompile>
+    <ClCompile Include="..\Tests\WebCore\ParsedContentRange.cpp">
+      <Filter>Tests\WebCore</Filter>
+    </ClCompile>
     <ClCompile Include="..\Tests\WebCore\TimeRanges.cpp">
       <Filter>Tests\WebCore</Filter>
     </ClCompile>
index a7dc549..07c0832 100644 (file)
                C540F784152E5A9A00A40C8C /* verboseMarkup.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C540F783152E5A7800A40C8C /* verboseMarkup.html */; };
                C54237F116B8957D00E638FC /* PasteboardNotifications_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C54237ED16B8955800E638FC /* PasteboardNotifications_Bundle.cpp */; };
                C5E1AFFE16B221F1006CC1F2 /* execCopy.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = C5E1AFFD16B22179006CC1F2 /* execCopy.html */; };
+               CD225C081C45A69200140761 /* ParsedContentRange.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD225C071C45A69200140761 /* ParsedContentRange.cpp */; };
                CD59F53419E9110D00CF1835 /* file-with-mse.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CD59F53219E910AA00CF1835 /* file-with-mse.html */; };
                CD59F53519E9110D00CF1835 /* test-mse.mp4 in Copy Resources */ = {isa = PBXBuildFile; fileRef = CD59F53319E910BC00CF1835 /* test-mse.mp4 */; };
                CDBFCC451A9FF45300A7B691 /* FullscreenZoomInitialFrame.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDBFCC431A9FF44800A7B691 /* FullscreenZoomInitialFrame.mm */; };
                C54237EE16B8955800E638FC /* PasteboardNotifications.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PasteboardNotifications.mm; sourceTree = "<group>"; };
                C5E1AFFD16B22179006CC1F2 /* execCopy.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = execCopy.html; sourceTree = "<group>"; };
                C95501BE19AD2FAF0049BE3E /* Preferences.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Preferences.mm; sourceTree = "<group>"; };
+               CD225C071C45A69200140761 /* ParsedContentRange.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParsedContentRange.cpp; sourceTree = "<group>"; };
                CD5393C71757BA9700C07123 /* MD5.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MD5.cpp; sourceTree = "<group>"; };
                CD5393C91757BAC400C07123 /* SHA1.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SHA1.cpp; sourceTree = "<group>"; };
                CD5451E919E41F9D0016936F /* CSSParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSParser.cpp; sourceTree = "<group>"; };
                                26F6E1EF1ADC749B00DE696B /* DFAMinimizer.cpp */,
                                41973B5A1AF2286A006C7B36 /* FileSystem.cpp */,
                                14464012167A8305000BD218 /* LayoutUnit.cpp */,
+                               CD225C071C45A69200140761 /* ParsedContentRange.cpp */,
                                41973B5C1AF22875006C7B36 /* SharedBuffer.cpp */,
                                CDC2C7141797089D00E627FB /* TimeRanges.cpp */,
                                440A1D3814A0103A008A66F2 /* URL.cpp */,
                                1CB9BC381A67482300FE5678 /* WeakPtr.cpp in Sources */,
                                2E7765CD16C4D80A00BA2BB1 /* mainIOS.mm in Sources */,
                                2D8104CC1BEC13E70020DA46 /* FindInPage.mm in Sources */,
+                               CD225C081C45A69200140761 /* ParsedContentRange.cpp in Sources */,
                                41973B5D1AF22875006C7B36 /* SharedBuffer.cpp in Sources */,
                                2DD355361BD08378005DF4A7 /* AutoLayoutIntegration.mm in Sources */,
                                7AA6A1521AAC0B31002B2ED3 /* WorkQueue.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/ParsedContentRange.cpp b/Tools/TestWebKitAPI/Tests/WebCore/ParsedContentRange.cpp
new file mode 100644 (file)
index 0000000..9863883
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <WebCore/ParsedContentRange.h>
+#include <wtf/text/WTFString.h>
+
+using namespace WebCore;
+
+namespace TestWebKitAPI {
+
+TEST(WebCore, ParsedContentRangeFromString)
+{
+    // Basic parsing
+    ASSERT_TRUE(ParsedContentRange("bytes 0-1/2").isValid());
+    ASSERT_TRUE(ParsedContentRange("bytes 0-1/*").isValid());
+    ASSERT_EQ(0, ParsedContentRange("bytes 0-1/2").firstBytePosition());
+    ASSERT_EQ(1, ParsedContentRange("bytes 0-1/2").lastBytePosition());
+    ASSERT_EQ(2, ParsedContentRange("bytes 0-1/2").instanceLength());
+    ASSERT_EQ(ParsedContentRange::UnknownLength, ParsedContentRange("bytes 0-1/*").instanceLength());
+
+    // Whitespace errors
+    ASSERT_FALSE(ParsedContentRange("bytes  0-1/*").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0 -1/*").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0- 1/*").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1 /*").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1/ *").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1/* ").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1/ 2").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1/2 ").isValid());
+
+    // Non-digit errors
+    ASSERT_FALSE(ParsedContentRange("bytes abcd-1/2").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-abcd/2").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1/abcd").isValid());
+
+    // Range requirement errors
+    ASSERT_FALSE(ParsedContentRange("bytes 1-0/2").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-2/1").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 2/0-1").isValid());
+    ASSERT_FALSE(ParsedContentRange("abcd 0/1-2").isValid());
+
+    // Negative value errors
+    ASSERT_FALSE(ParsedContentRange("bytes -0-1/*").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes -1/*").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0--0/2").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 0-1/-2").isValid());
+
+    // Edge cases
+    ASSERT_TRUE(ParsedContentRange("bytes 9223372036854775805-9223372036854775806/9223372036854775807").isValid());
+    ASSERT_FALSE(ParsedContentRange("bytes 9223372036854775808-9223372036854775809/9223372036854775810").isValid());
+}
+
+TEST(WebCore, ParsedContentRangeFromValues)
+{
+    ASSERT_TRUE(ParsedContentRange(0, 1, 2).isValid());
+    ASSERT_TRUE(ParsedContentRange(0, 1, ParsedContentRange::UnknownLength).isValid());
+    ASSERT_FALSE(ParsedContentRange().isValid());
+    ASSERT_FALSE(ParsedContentRange(1, 0, 2).isValid());
+    ASSERT_FALSE(ParsedContentRange(0, 2, 1).isValid());
+    ASSERT_FALSE(ParsedContentRange(0, 0, 0).isValid());
+    ASSERT_FALSE(ParsedContentRange(-1, 1, 2).isValid());
+    ASSERT_FALSE(ParsedContentRange(0, -1, 2).isValid());
+    ASSERT_FALSE(ParsedContentRange(0, 1, -2).isValid());
+    ASSERT_FALSE(ParsedContentRange(-2, -1, 2).isValid());
+}
+
+TEST(WebCore, ParsedContentRangeToString)
+{
+    ASSERT_STREQ("bytes 0-1/2", ParsedContentRange(0, 1, 2).headerValue().utf8().data());
+    ASSERT_STREQ("bytes 0-1/*", ParsedContentRange(0, 1, ParsedContentRange::UnknownLength).headerValue().utf8().data());
+    ASSERT_STREQ("", ParsedContentRange().headerValue().utf8().data());
+}
+
+}