DataURLDecoder.cpp [plain text]
#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", WorkQueue::Type::Serial, WorkQueue::QOS::UserInitiated).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;
};
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, Mode mode)
{
Vector<char> buffer;
if (mode == Mode::ForgivingBase64) {
auto unescapedString = decodeURLEscapeSequences(task.encodedData.toStringWithoutCopying());
if (!base64Decode(unescapedString, buffer, Base64ValidatePadding | Base64IgnoreSpacesAndNewLines | Base64DiscardVerticalTab))
return;
} else {
if (!base64URLDecode(task.encodedData.toStringWithoutCopying(), buffer)) {
auto unescapedString = decodeURLEscapeSequences(task.encodedData.toStringWithoutCopying());
if (!base64Decode(unescapedString, buffer, Base64IgnoreSpacesAndNewLines | Base64DiscardVerticalTab))
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, Mode mode, DecodeCompletionHandler&& completionHandler)
{
ASSERT(url.protocolIsData());
decodeQueue().dispatch([decodeTask = createDecodeTask(url, scheduleContext, WTFMove(completionHandler)), mode]() mutable {
if (decodeTask->process()) {
if (decodeTask->isBase64)
decodeBase64(*decodeTask, mode);
else
decodeEscaped(*decodeTask);
}
#if USE(COCOA_EVENT_LOOP)
auto scheduledPairs = decodeTask->scheduleContext.scheduledPairs;
#endif
auto callCompletionHandler = [decodeTask = WTFMove(decodeTask)] {
if (!decodeTask->result.data) {
decodeTask->completionHandler({ });
return;
}
decodeTask->completionHandler(WTFMove(decodeTask->result));
};
#if USE(COCOA_EVENT_LOOP)
RunLoop::dispatch(scheduledPairs, WTFMove(callCompletionHandler));
#else
RunLoop::main().dispatch(WTFMove(callCompletionHandler));
#endif
});
}
}
}