1/*
2 * Copyright (C) 2015 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DataURLDecoder.h"
28
29#include "DecodeEscapeSequences.h"
30#include "HTTPParsers.h"
31#include "ParsedContentType.h"
32#include "SharedBuffer.h"
33#include "TextEncoding.h"
34#include <wtf/MainThread.h>
35#include <wtf/Optional.h>
36#include <wtf/RunLoop.h>
37#include <wtf/URL.h>
38#include <wtf/WorkQueue.h>
39#include <wtf/text/Base64.h>
40
41namespace WebCore {
42namespace DataURLDecoder {
43
44static WorkQueue& decodeQueue()
45{
46 static auto& queue = WorkQueue::create("org.webkit.DataURLDecoder").leakRef();
47 return queue;
48}
49
50static Result parseMediaType(const String& mediaType)
51{
52 if (Optional<ParsedContentType> parsedContentType = ParsedContentType::create(mediaType))
53 return { parsedContentType->mimeType(), parsedContentType->charset(), parsedContentType->serialize(), nullptr };
54 return { "text/plain"_s, "US-ASCII"_s, "text/plain;charset=US-ASCII"_s, nullptr };
55}
56
57struct DecodeTask {
58 WTF_MAKE_FAST_ALLOCATED;
59public:
60 DecodeTask(const String& urlString, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
61 : urlString(urlString.isolatedCopy())
62 , scheduleContext(scheduleContext)
63 , completionHandler(WTFMove(completionHandler))
64 {
65 }
66
67 bool process()
68 {
69 if (urlString.find(',') == notFound)
70 return false;
71 const char dataString[] = "data:";
72 const char base64String[] = ";base64";
73
74 ASSERT(urlString.startsWith(dataString));
75
76 size_t headerEnd = urlString.find(',', strlen(dataString));
77 size_t encodedDataStart = headerEnd == notFound ? headerEnd : headerEnd + 1;
78
79 encodedData = StringView(urlString).substring(encodedDataStart);
80 auto header = StringView(urlString).substring(strlen(dataString), headerEnd - strlen(dataString));
81 isBase64 = header.endsWithIgnoringASCIICase(StringView(base64String));
82 auto mediaType = (isBase64 ? header.substring(0, header.length() - strlen(base64String)) : header).toString();
83 mediaType = mediaType.stripWhiteSpace();
84 if (mediaType.startsWith(';'))
85 mediaType.insert("text/plain", 0);
86 result = parseMediaType(mediaType);
87
88 return true;
89 }
90
91 const String urlString;
92 StringView encodedData;
93 bool isBase64 { false };
94 const ScheduleContext scheduleContext;
95 const DecodeCompletionHandler completionHandler;
96
97 Result result;
98};
99
100#if HAVE(RUNLOOP_TIMER)
101
102class DecodingResultDispatcher : public ThreadSafeRefCounted<DecodingResultDispatcher> {
103public:
104 static void dispatch(std::unique_ptr<DecodeTask> decodeTask)
105 {
106 Ref<DecodingResultDispatcher> dispatcher = adoptRef(*new DecodingResultDispatcher(WTFMove(decodeTask)));
107 dispatcher->startTimer();
108 }
109
110private:
111 DecodingResultDispatcher(std::unique_ptr<DecodeTask> decodeTask)
112 : m_timer(*this, &DecodingResultDispatcher::timerFired)
113 , m_decodeTask(WTFMove(decodeTask))
114 {
115 }
116
117 void startTimer()
118 {
119 // Keep alive until the timer has fired.
120 ref();
121
122 auto scheduledPairs = m_decodeTask->scheduleContext.scheduledPairs;
123 m_timer.startOneShot(0_s);
124 m_timer.schedule(scheduledPairs);
125 }
126
127 void timerFired()
128 {
129 if (m_decodeTask->result.data)
130 m_decodeTask->completionHandler(WTFMove(m_decodeTask->result));
131 else
132 m_decodeTask->completionHandler({ });
133
134 // Ensure DecodeTask gets deleted in the main thread.
135 m_decodeTask = nullptr;
136
137 deref();
138 }
139
140 RunLoopTimer<DecodingResultDispatcher> m_timer;
141 std::unique_ptr<DecodeTask> m_decodeTask;
142};
143
144#endif // HAVE(RUNLOOP_TIMER)
145
146static std::unique_ptr<DecodeTask> createDecodeTask(const URL& url, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
147{
148 return std::make_unique<DecodeTask>(
149 url.string(),
150 scheduleContext,
151 WTFMove(completionHandler)
152 );
153}
154
155static void decodeBase64(DecodeTask& task)
156{
157 Vector<char> buffer;
158 // First try base64url.
159 if (!base64URLDecode(task.encodedData.toStringWithoutCopying(), buffer)) {
160 // Didn't work, try unescaping and decoding as base64.
161 auto unescapedString = decodeURLEscapeSequences(task.encodedData.toStringWithoutCopying());
162 if (!base64Decode(unescapedString, buffer, Base64IgnoreSpacesAndNewLines))
163 return;
164 }
165 buffer.shrinkToFit();
166 task.result.data = SharedBuffer::create(WTFMove(buffer));
167}
168
169static void decodeEscaped(DecodeTask& task)
170{
171 TextEncoding encodingFromCharset(task.result.charset);
172 auto& encoding = encodingFromCharset.isValid() ? encodingFromCharset : UTF8Encoding();
173 auto buffer = decodeURLEscapeSequencesAsData(task.encodedData, encoding);
174
175 buffer.shrinkToFit();
176 task.result.data = SharedBuffer::create(WTFMove(buffer));
177}
178
179void decode(const URL& url, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
180{
181 ASSERT(url.protocolIsData());
182
183 decodeQueue().dispatch([decodeTask = createDecodeTask(url, scheduleContext, WTFMove(completionHandler))]() mutable {
184 if (decodeTask->process()) {
185 if (decodeTask->isBase64)
186 decodeBase64(*decodeTask);
187 else
188 decodeEscaped(*decodeTask);
189 }
190
191#if HAVE(RUNLOOP_TIMER)
192 DecodingResultDispatcher::dispatch(WTFMove(decodeTask));
193#else
194 callOnMainThread([decodeTask = WTFMove(decodeTask)] {
195 if (!decodeTask->result.data) {
196 decodeTask->completionHandler({ });
197 return;
198 }
199 decodeTask->completionHandler(WTFMove(decodeTask->result));
200 });
201#endif
202 });
203}
204
205}
206}
207