blob: b541f62ee00520362c1557f7ca4964a237b8749d [file] [log] [blame]
/*
* 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 "ParsedContentType.h"
#include "SharedBuffer.h"
#include "TextEncoding.h"
#include <wtf/MainThread.h>
#include <wtf/Optional.h>
#include <wtf/RunLoop.h>
#include <wtf/URL.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;
}
static Result parseMediaType(const String& mediaType)
{
if (Optional<ParsedContentType> parsedContentType = ParsedContentType::create(mediaType))
return { parsedContentType->mimeType(), parsedContentType->charset(), parsedContentType->serialize(), nullptr };
return { "text/plain"_s, "US-ASCII"_s, "text/plain;charset=US-ASCII"_s, nullptr };
}
struct DecodeTask {
WTF_MAKE_FAST_ALLOCATED;
public:
DecodeTask(const String& urlString, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
: urlString(urlString.isolatedCopy())
, scheduleContext(scheduleContext)
, completionHandler(WTFMove(completionHandler))
{
}
bool process()
{
if (urlString.find(',') == notFound)
return false;
const char dataString[] = "data:";
const char base64String[] = ";base64";
ASSERT(urlString.startsWith(dataString));
size_t headerEnd = urlString.find(',', strlen(dataString));
size_t encodedDataStart = headerEnd == notFound ? headerEnd : headerEnd + 1;
encodedData = StringView(urlString).substring(encodedDataStart);
auto header = StringView(urlString).substring(strlen(dataString), headerEnd - strlen(dataString));
isBase64 = header.endsWithIgnoringASCIICase(StringView(base64String));
auto mediaType = (isBase64 ? header.substring(0, header.length() - strlen(base64String)) : header).toString();
mediaType = mediaType.stripWhiteSpace();
if (mediaType.startsWith(';'))
mediaType.insert("text/plain", 0);
result = parseMediaType(mediaType);
return true;
}
const String urlString;
StringView encodedData;
bool isBase64 { false };
const ScheduleContext scheduleContext;
const DecodeCompletionHandler completionHandler;
Result result;
};
#if HAVE(RUNLOOP_TIMER)
class DecodingResultDispatcher : public ThreadSafeRefCounted<DecodingResultDispatcher> {
public:
static void dispatch(std::unique_ptr<DecodeTask> decodeTask)
{
Ref<DecodingResultDispatcher> dispatcher = adoptRef(*new DecodingResultDispatcher(WTFMove(decodeTask)));
dispatcher->startTimer();
}
private:
DecodingResultDispatcher(std::unique_ptr<DecodeTask> decodeTask)
: m_timer(*this, &DecodingResultDispatcher::timerFired)
, m_decodeTask(WTFMove(decodeTask))
{
}
void startTimer()
{
// Keep alive until the timer has fired.
ref();
auto scheduledPairs = m_decodeTask->scheduleContext.scheduledPairs;
m_timer.startOneShot(0_s);
m_timer.schedule(scheduledPairs);
}
void timerFired()
{
if (m_decodeTask->result.data)
m_decodeTask->completionHandler(WTFMove(m_decodeTask->result));
else
m_decodeTask->completionHandler({ });
// Ensure DecodeTask gets deleted in the main thread.
m_decodeTask = nullptr;
deref();
}
RunLoopTimer<DecodingResultDispatcher> m_timer;
std::unique_ptr<DecodeTask> m_decodeTask;
};
#endif // HAVE(RUNLOOP_TIMER)
static std::unique_ptr<DecodeTask> createDecodeTask(const URL& url, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
{
return makeUnique<DecodeTask>(
url.string(),
scheduleContext,
WTFMove(completionHandler)
);
}
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, Base64IgnoreSpacesAndNewLines))
return;
}
buffer.shrinkToFit();
task.result.data = SharedBuffer::create(WTFMove(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::create(WTFMove(buffer));
}
void decode(const URL& url, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
{
ASSERT(url.protocolIsData());
decodeQueue().dispatch([decodeTask = createDecodeTask(url, scheduleContext, WTFMove(completionHandler))]() mutable {
if (decodeTask->process()) {
if (decodeTask->isBase64)
decodeBase64(*decodeTask);
else
decodeEscaped(*decodeTask);
}
#if HAVE(RUNLOOP_TIMER)
DecodingResultDispatcher::dispatch(WTFMove(decodeTask));
#else
callOnMainThread([decodeTask = WTFMove(decodeTask)] {
if (!decodeTask->result.data) {
decodeTask->completionHandler({ });
return;
}
decodeTask->completionHandler(WTFMove(decodeTask->result));
});
#endif
});
}
}
}