Skip to content
Permalink
Browse files
Decode data URLs in web process
https://bugs.webkit.org/show_bug.cgi?id=148128

Reviewed by Darin Adler.

Source/WebCore:

We currenly send data URLs to networking layer for decoding. This involves a long and slow roundtrip through IPC and API layers.

* WebCore.xcodeproj/project.pbxproj:
* loader/ResourceLoadScheduler.cpp:
(WebCore::ResourceLoadScheduler::maybeLoadQuickLookResource): Deleted.

    Remove this awkward interface for WebKit2.

* loader/ResourceLoadScheduler.h:
* loader/ResourceLoader.cpp:
(WebCore::ResourceLoader::start):
(WebCore::ResourceLoader::loadDataURL):

    Load using DataURLDecoder.

* loader/ResourceLoader.h:

    Make start() public and export it so it can be called directly from WebKit2.

* platform/network/DataURLDecoder.cpp: Added.
(WebCore::DataURLDecoder::decodeQueue):
(WebCore::DataURLDecoder::DecodeTask::DecodeTask):
(WebCore::DataURLDecoder::createDecodeTask):

    Parse data URL metadata and initialize the decode task.

(WebCore::DataURLDecoder::decodeBase64):
(WebCore::DataURLDecoder::decodeEscaped):
(WebCore::DataURLDecoder::decode):

    Asynchronously decode in a concurrent distpatch queue.

* platform/network/DataURLDecoder.h: Added.
* platform/network/ios/QuickLook.h:
* platform/text/DecodeEscapeSequences.h:
(WebCore::URLEscapeSequence::findEndOfRun):
(WebCore::URLEscapeSequence::decodeRun):

    Factor buffer generation to a function.

(WebCore::decodeEscapeSequences):
(WebCore::decodeURLEscapeSequencesAsData):

    Add decode function that produces bytes instead of a String.

Source/WebKit2:

* WebProcess/Network/WebResourceLoadScheduler.cpp:
(WebKit::WebResourceLoadScheduler::scheduleLoad):

    Don't send data: loads to the network process, handle them locally.

(WebKit::WebResourceLoadScheduler::startLocalLoad):

    Call ResourceLoder::start() directly.


Canonical link: https://commits.webkit.org/166454@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@188820 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
anttijk committed Aug 22, 2015
1 parent 535c8f7 commit 4aabae6440c893fda881560198a80e07d906ca43
@@ -2253,6 +2253,7 @@ set(WebCore_SOURCES
platform/network/CredentialStorage.cpp
platform/network/DNSResolveQueue.cpp
platform/network/DataURL.cpp
platform/network/DataURLDecoder.cpp
platform/network/FormData.cpp
platform/network/FormDataBuilder.cpp
platform/network/HTTPHeaderMap.cpp
@@ -1,3 +1,55 @@
2015-08-22 Antti Koivisto <antti@apple.com>

Decode data URLs in web process
https://bugs.webkit.org/show_bug.cgi?id=148128

Reviewed by Darin Adler.

We currenly send data URLs to networking layer for decoding. This involves a long and slow roundtrip through IPC and API layers.

* WebCore.xcodeproj/project.pbxproj:
* loader/ResourceLoadScheduler.cpp:
(WebCore::ResourceLoadScheduler::maybeLoadQuickLookResource): Deleted.

Remove this awkward interface for WebKit2.

* loader/ResourceLoadScheduler.h:
* loader/ResourceLoader.cpp:
(WebCore::ResourceLoader::start):
(WebCore::ResourceLoader::loadDataURL):

Load using DataURLDecoder.

* loader/ResourceLoader.h:

Make start() public and export it so it can be called directly from WebKit2.

* platform/network/DataURLDecoder.cpp: Added.
(WebCore::DataURLDecoder::decodeQueue):
(WebCore::DataURLDecoder::DecodeTask::DecodeTask):
(WebCore::DataURLDecoder::createDecodeTask):

Parse data URL metadata and initialize the decode task.

(WebCore::DataURLDecoder::decodeBase64):
(WebCore::DataURLDecoder::decodeEscaped):
(WebCore::DataURLDecoder::decode):

Asynchronously decode in a concurrent distpatch queue.

* platform/network/DataURLDecoder.h: Added.
* platform/network/ios/QuickLook.h:
* platform/text/DecodeEscapeSequences.h:
(WebCore::URLEscapeSequence::findEndOfRun):
(WebCore::URLEscapeSequence::decodeRun):

Factor buffer generation to a function.

(WebCore::decodeEscapeSequences):
(WebCore::decodeURLEscapeSequencesAsData):

Add decode function that produces bytes instead of a String.

2015-08-21 Commit Queue <commit-queue@webkit.org>

Unreviewed, rolling out r188792 and r188803.
@@ -8820,6 +8820,7 @@
<ClCompile Include="..\platform\network\CredentialBase.cpp" />
<ClCompile Include="..\platform\network\CredentialStorage.cpp" />
<ClCompile Include="..\platform\network\DataURL.cpp" />
<ClCompile Include="..\platform\network\DataURLDecoder.cpp" />
<ClCompile Include="..\platform\network\DNSResolveQueue.cpp" />
<ClCompile Include="..\platform\network\FormData.cpp" />
<ClCompile Include="..\platform\network\FormDataBuilder.cpp" />
@@ -21331,6 +21332,7 @@
<ClInclude Include="..\platform\network\CredentialBase.h" />
<ClInclude Include="..\platform\network\CredentialStorage.h" />
<ClInclude Include="..\platform\network\DataURL.h" />
<ClInclude Include="..\platform\network\DataURLDecoder.h" />
<ClInclude Include="..\platform\network\DNSResolveQueue.h" />
<ClInclude Include="..\platform\network\FormData.h" />
<ClInclude Include="..\platform\network\FormDataBuilder.h" />
@@ -6372,6 +6372,8 @@
E4946EAF156E64DD00D3297F /* StyleRuleImport.h in Headers */ = {isa = PBXBuildFile; fileRef = E4946EAD156E64DD00D3297F /* StyleRuleImport.h */; };
E49BD9FA131FD2ED003C56F0 /* CSSValuePool.h in Headers */ = {isa = PBXBuildFile; fileRef = E49BD9F9131FD2ED003C56F0 /* CSSValuePool.h */; };
E49BDA0B131FD3E5003C56F0 /* CSSValuePool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E49BDA0A131FD3E5003C56F0 /* CSSValuePool.cpp */; };
E4A007831B820EC8002C5A6E /* DataURLDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = E4A007821B820EC8002C5A6E /* DataURLDecoder.h */; };
E4A007851B820ED3002C5A6E /* DataURLDecoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4A007841B820ED3002C5A6E /* DataURLDecoder.cpp */; };
E4AE7C1617D1BB950009FB31 /* ElementIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AE7C1517D1BB950009FB31 /* ElementIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
E4AE7C1A17D232350009FB31 /* ElementAncestorIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AE7C1917D232350009FB31 /* ElementAncestorIterator.h */; settings = {ATTRIBUTES = (Private, ); }; };
E4AFCFA50DAF29A300F5F55C /* UnitBezier.h in Headers */ = {isa = PBXBuildFile; fileRef = E4AFCFA40DAF29A300F5F55C /* UnitBezier.h */; };
@@ -14081,6 +14083,8 @@
E4946EAD156E64DD00D3297F /* StyleRuleImport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StyleRuleImport.h; sourceTree = "<group>"; };
E49BD9F9131FD2ED003C56F0 /* CSSValuePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSValuePool.h; sourceTree = "<group>"; };
E49BDA0A131FD3E5003C56F0 /* CSSValuePool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSValuePool.cpp; sourceTree = "<group>"; };
E4A007821B820EC8002C5A6E /* DataURLDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataURLDecoder.h; sourceTree = "<group>"; };
E4A007841B820ED3002C5A6E /* DataURLDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataURLDecoder.cpp; sourceTree = "<group>"; };
E4AE7C1517D1BB950009FB31 /* ElementIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ElementIterator.h; sourceTree = "<group>"; };
E4AE7C1917D232350009FB31 /* ElementAncestorIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ElementAncestorIterator.h; sourceTree = "<group>"; };
E4AFCFA40DAF29A300F5F55C /* UnitBezier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnitBezier.h; sourceTree = "<group>"; };
@@ -16879,6 +16883,8 @@
379291781985EF3900F4B661 /* CredentialBase.h */,
51A052321058774F00CC9E95 /* CredentialStorage.cpp */,
51A052311058774F00CC9E95 /* CredentialStorage.h */,
E4A007841B820ED3002C5A6E /* DataURLDecoder.cpp */,
E4A007821B820EC8002C5A6E /* DataURLDecoder.h */,
B2F34FE50E82F81400F627CD /* DNS.h */,
7C60128060078BB70E367A95 /* DNSResolveQueue.cpp */,
FA6E466FCD0418A9966A5B60 /* DNSResolveQueue.h */,
@@ -26074,6 +26080,7 @@
C9D851F01B39DC780085062E /* MediaSessionMetadata.h in Headers */,
CD3A495F17A9D01B00274E42 /* MediaSource.h in Headers */,
CD641EB31818F5ED00EE4C41 /* MediaSourcePrivate.h in Headers */,
E4A007831B820EC8002C5A6E /* DataURLDecoder.h in Headers */,
CDC8B5A7180474F70016E685 /* MediaSourcePrivateAVFObjC.h in Headers */,
CDDC1E7A18A952F30027A9D4 /* MediaSourcePrivateClient.h in Headers */,
CD61FE681794AADB004101EB /* MediaSourceRegistry.h in Headers */,
@@ -28408,6 +28415,7 @@
15C770A5100D41CD005BA267 /* DOMValidityState.mm in Sources */,
31C0FF4A0E4CEFDD007D6FE5 /* DOMWebKitAnimationEvent.mm in Sources */,
3106037A143281CD00ABF4BA /* DOMWebKitCSSFilterValue.mm in Sources */,
E4A007851B820ED3002C5A6E /* DataURLDecoder.cpp in Sources */,
498391510F1E76B400C23782 /* DOMWebKitCSSMatrix.mm in Sources */,
8AD0A59714C88358000D83C5 /* DOMWebKitCSSRegionRule.mm in Sources */,
31611E620E1C4E1400F6A579 /* DOMWebKitCSSTransformValue.mm in Sources */,
@@ -195,17 +195,6 @@ void ResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader)
scheduleServePendingRequests();
}

#if USE(QUICK_LOOK)
bool ResourceLoadScheduler::maybeLoadQuickLookResource(ResourceLoader& loader)
{
if (!loader.request().url().protocolIs(QLPreviewProtocol()))
return false;

loader.start();
return true;
}
#endif

void ResourceLoadScheduler::remove(ResourceLoader* resourceLoader)
{
ASSERT(resourceLoader);
@@ -76,10 +76,6 @@ class ResourceLoadScheduler {
WEBCORE_EXPORT ResourceLoadScheduler();
WEBCORE_EXPORT virtual ~ResourceLoadScheduler();

#if USE(QUICK_LOOK)
WEBCORE_EXPORT bool maybeLoadQuickLookResource(ResourceLoader&);
#endif

private:
void scheduleLoad(ResourceLoader*);
void scheduleServePendingRequests();
@@ -32,6 +32,7 @@

#include "ApplicationCacheHost.h"
#include "AuthenticationChallenge.h"
#include "DataURLDecoder.h"
#include "DiagnosticLoggingClient.h"
#include "DiagnosticLoggingKeys.h"
#include "DocumentLoader.h"
@@ -202,10 +203,15 @@ void ResourceLoader::start()
return;
}

if (!m_reachedTerminalState) {
FrameLoader& loader = m_request.url().protocolIsData() ? dataProtocolFrameLoader() : *frameLoader();
m_handle = ResourceHandle::create(loader.networkingContext(), m_request, this, m_defersLoading, m_options.sniffContent() == SniffContent);
if (m_reachedTerminalState)
return;

if (m_request.url().protocolIsData()) {
loadDataURL();
return;
}

m_handle = ResourceHandle::create(frameLoader()->networkingContext(), m_request, this, m_defersLoading, m_options.sniffContent() == SniffContent);
}

void ResourceLoader::setDefersLoading(bool defers)
@@ -229,6 +235,33 @@ FrameLoader* ResourceLoader::frameLoader() const
return &m_frame->loader();
}

void ResourceLoader::loadDataURL()
{
auto url = m_request.url();
ASSERT(url.protocolIsData());

RefPtr<ResourceLoader> loader(this);
DataURLDecoder::decode(url, [loader, url] (Optional<DataURLDecoder::Result> decodeResult) {
if (loader->reachedTerminalState())
return;
if (!decodeResult) {
loader->didFail(ResourceError(errorDomainWebKitInternal, 0, url.string(), "Data URL decoding failed"));
return;
}
auto& result = decodeResult.value();
auto dataSize = result.data->size();

ResourceResponse dataResponse { url, result.mimeType, dataSize, result.charset };
loader->didReceiveResponse(dataResponse);

if (!loader->reachedTerminalState() && dataSize)
loader->didReceiveBuffer(result.data.get(), dataSize, DataPayloadWholeResource);

if (!loader->reachedTerminalState())
loader->didFinishLoading(currentTime());
});
}

// This function should only be called when frameLoader() is non-null.
FrameLoader& ResourceLoader::dataProtocolFrameLoader() const
{
@@ -80,7 +80,8 @@ class ResourceLoader : public RefCounted<ResourceLoader>, protected ResourceHand
FrameLoader& dataProtocolFrameLoader() const;
DocumentLoader* documentLoader() const { return m_documentLoader.get(); }
const ResourceRequest& originalRequest() const { return m_originalRequest; }


WEBCORE_EXPORT void start();
WEBCORE_EXPORT void cancel(const ResourceError&);
WEBCORE_EXPORT ResourceError cancelledError();
ResourceError blockedError();
@@ -148,11 +149,6 @@ class ResourceLoader : public RefCounted<ResourceLoader>, protected ResourceHand
protected:
ResourceLoader(Frame*, ResourceLoaderOptions);

friend class ResourceLoadScheduler; // for access to start()
// start() actually sends the load to the network (unless the load is being
// deferred) and should only be called by ResourceLoadScheduler or setDefersLoading().
void start();

void didFinishLoadingOnePart(double finishTime);
void cleanupForError(const ResourceError&);

@@ -181,6 +177,7 @@ class ResourceLoader : public RefCounted<ResourceLoader>, protected ResourceHand
virtual void didCancel(const ResourceError&) = 0;

void addDataOrBuffer(const char*, unsigned, SharedBuffer*, DataPayloadType);
void loadDataURL();

// ResourceHandleClient
virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse& redirectResponse) override;
@@ -0,0 +1,148 @@
/*
* Copyright (C) 2015 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 "DataURLDecoder.h"

#include "DecodeEscapeSequences.h"
#include "HTTPParsers.h"
#include "SharedBuffer.h"
#include "URL.h"
#include <wtf/MainThread.h>
#include <wtf/RunLoop.h>
#include <wtf/WorkQueue.h>
#include <wtf/text/Base64.h>

namespace WebCore {
namespace DataURLDecoder {

static WorkQueue& decodeQueue()
{
static auto& queue = WorkQueue::create("org.webkit.DataURLDecoder").leakRef();
return queue;
}

struct DecodeTask {
const String urlString;
const StringView encodedData;
const bool isBase64;
const DecodeCompletionHandler completionHandler;

Result result;
};

static Result parseMediaType(const String& mediaType)
{
auto mimeType = extractMIMETypeFromMediaType(mediaType);
auto charset = extractCharsetFromMediaType(mediaType);

// https://tools.ietf.org/html/rfc2397
// If <mediatype> is omitted, it defaults to text/plain;charset=US-ASCII. As a shorthand,
// "text/plain" can be omitted but the charset parameter supplied.
if (mimeType.isEmpty()) {
mimeType = ASCIILiteral("text/plain");
if (charset.isEmpty())
charset = ASCIILiteral("US-ASCII");
}
return { mimeType, charset, nullptr };
}

static std::unique_ptr<DecodeTask> createDecodeTask(const URL& url, DecodeCompletionHandler completionHandler)
{
const char dataString[] = "data:";
const char base64String[] = ";base64";

auto urlString = url.string();
ASSERT(urlString.startsWith(dataString));

size_t headerEnd = urlString.find(',', strlen(dataString));
size_t encodedDataStart = headerEnd == notFound ? headerEnd : headerEnd + 1;

auto encodedData = StringView(urlString).substring(encodedDataStart);
auto header = StringView(urlString).substring(strlen(dataString), headerEnd - strlen(dataString));
bool isBase64 = header.endsWithIgnoringASCIICase(StringView(base64String));
auto mediaType = (isBase64 ? header.substring(0, header.length() - strlen(base64String)) : header).toString();

return std::make_unique<DecodeTask>(DecodeTask {
WTF::move(urlString),
WTF::move(encodedData),
isBase64,
WTF::move(completionHandler),
parseMediaType(mediaType)
});
}

static void decodeBase64(DecodeTask& task)
{
Vector<char> buffer;
// First try base64url.
if (!base64URLDecode(task.encodedData.toStringWithoutCopying(), buffer)) {
// Didn't work, try unescaping and decoding as base64.
auto unescapedString = decodeURLEscapeSequences(task.encodedData.toStringWithoutCopying());
if (!base64Decode(unescapedString, buffer, Base64IgnoreWhitespace))
return;
}
buffer.shrinkToFit();
task.result.data = SharedBuffer::adoptVector(buffer);
}

static void decodeEscaped(DecodeTask& task)
{
TextEncoding encodingFromCharset(task.result.charset);
auto& encoding = encodingFromCharset.isValid() ? encodingFromCharset : UTF8Encoding();
auto buffer = decodeURLEscapeSequencesAsData(task.encodedData, encoding);

buffer.shrinkToFit();
task.result.data = SharedBuffer::adoptVector(buffer);
}

void decode(const URL& url, DecodeCompletionHandler completionHandler)
{
ASSERT(url.protocolIsData());

auto decodeTask = createDecodeTask(url, WTF::move(completionHandler));

auto* decodeTaskPtr = decodeTask.release();
decodeQueue().dispatch([decodeTaskPtr] {
auto& decodeTask = *decodeTaskPtr;

if (decodeTask.isBase64)
decodeBase64(decodeTask);
else
decodeEscaped(decodeTask);

callOnMainThread([decodeTaskPtr] {
std::unique_ptr<DecodeTask> decodeTask(decodeTaskPtr);
if (!decodeTask->result.data) {
decodeTask->completionHandler({ });
return;
}
decodeTask->completionHandler(WTF::move(decodeTask->result));
});
});
}

}
}

0 comments on commit 4aabae6

Please sign in to comment.