Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / platform / network / DataURLDecoder.cpp
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 "SharedBuffer.h"
32 #include "TextEncoding.h"
33 #include <wtf/MainThread.h>
34 #include <wtf/RunLoop.h>
35 #include <wtf/URL.h>
36 #include <wtf/WorkQueue.h>
37 #include <wtf/text/Base64.h>
38
39 namespace WebCore {
40 namespace DataURLDecoder {
41
42 static WorkQueue& decodeQueue()
43 {
44     static auto& queue = WorkQueue::create("org.webkit.DataURLDecoder").leakRef();
45     return queue;
46 }
47
48 struct DecodeTask {
49     WTF_MAKE_FAST_ALLOCATED;
50 public:
51     DecodeTask(const String& urlString, StringView&& encodedData, bool isBase64, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler, Result&& result)
52         : urlString(urlString.isolatedCopy())
53         , encodedData(WTFMove(encodedData))
54         , isBase64(isBase64)
55         , scheduleContext(scheduleContext)
56         , completionHandler(WTFMove(completionHandler))
57         , result(WTFMove(result))
58     {
59     }
60
61     const String urlString;
62     const StringView encodedData;
63     const bool isBase64;
64     const ScheduleContext scheduleContext;
65     const DecodeCompletionHandler completionHandler;
66
67     Result result;
68 };
69
70 #if HAVE(RUNLOOP_TIMER)
71
72 class DecodingResultDispatcher : public ThreadSafeRefCounted<DecodingResultDispatcher> {
73 public:
74     static void dispatch(std::unique_ptr<DecodeTask> decodeTask)
75     {
76         Ref<DecodingResultDispatcher> dispatcher = adoptRef(*new DecodingResultDispatcher(WTFMove(decodeTask)));
77         dispatcher->startTimer();
78     }
79
80 private:
81     DecodingResultDispatcher(std::unique_ptr<DecodeTask> decodeTask)
82         : m_timer(*this, &DecodingResultDispatcher::timerFired)
83         , m_decodeTask(WTFMove(decodeTask))
84     {
85     }
86
87     void startTimer()
88     {
89         // Keep alive until the timer has fired.
90         ref();
91
92         auto scheduledPairs = m_decodeTask->scheduleContext.scheduledPairs;
93         m_timer.startOneShot(0_s);
94         m_timer.schedule(scheduledPairs);
95     }
96
97     void timerFired()
98     {
99         if (m_decodeTask->result.data)
100             m_decodeTask->completionHandler(WTFMove(m_decodeTask->result));
101         else
102             m_decodeTask->completionHandler({ });
103
104         // Ensure DecodeTask gets deleted in the main thread.
105         m_decodeTask = nullptr;
106
107         deref();
108     }
109
110     RunLoopTimer<DecodingResultDispatcher> m_timer;
111     std::unique_ptr<DecodeTask> m_decodeTask;
112 };
113
114 #endif // HAVE(RUNLOOP_TIMER)
115
116 static Result parseMediaType(const String& mediaType)
117 {
118     auto mimeType = extractMIMETypeFromMediaType(mediaType);
119     auto charset = extractCharsetFromMediaType(mediaType);
120
121     // https://tools.ietf.org/html/rfc2397
122     // If <mediatype> is omitted, it defaults to text/plain;charset=US-ASCII. As a shorthand,
123     // "text/plain" can be omitted but the charset parameter supplied.
124     if (mimeType.isEmpty()) {
125         mimeType = "text/plain"_s;
126         if (charset.isEmpty())
127             charset = "US-ASCII"_s;
128     }
129     return { mimeType, charset, !mediaType.isEmpty() ? mediaType : "text/plain;charset=US-ASCII", nullptr };
130 }
131
132 static std::unique_ptr<DecodeTask> createDecodeTask(const URL& url, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
133 {
134     const char dataString[] = "data:";
135     const char base64String[] = ";base64";
136
137     auto urlString = url.string();
138     ASSERT(urlString.startsWith(dataString));
139
140     size_t headerEnd = urlString.find(',', strlen(dataString));
141     size_t encodedDataStart = headerEnd == notFound ? headerEnd : headerEnd + 1;
142
143     auto encodedData = StringView(urlString).substring(encodedDataStart);
144     auto header = StringView(urlString).substring(strlen(dataString), headerEnd - strlen(dataString));
145     bool isBase64 = header.endsWithIgnoringASCIICase(StringView(base64String));
146     auto mediaType = (isBase64 ? header.substring(0, header.length() - strlen(base64String)) : header).toString();
147
148     return std::make_unique<DecodeTask>(
149         urlString,
150         WTFMove(encodedData),
151         isBase64,
152         scheduleContext,
153         WTFMove(completionHandler),
154         parseMediaType(mediaType)
155     );
156 }
157
158 static void decodeBase64(DecodeTask& task)
159 {
160     Vector<char> buffer;
161     // First try base64url.
162     if (!base64URLDecode(task.encodedData.toStringWithoutCopying(), buffer)) {
163         // Didn't work, try unescaping and decoding as base64.
164         auto unescapedString = decodeURLEscapeSequences(task.encodedData.toStringWithoutCopying());
165         if (!base64Decode(unescapedString, buffer, Base64IgnoreSpacesAndNewLines))
166             return;
167     }
168     buffer.shrinkToFit();
169     task.result.data = SharedBuffer::create(WTFMove(buffer));
170 }
171
172 static void decodeEscaped(DecodeTask& task)
173 {
174     TextEncoding encodingFromCharset(task.result.charset);
175     auto& encoding = encodingFromCharset.isValid() ? encodingFromCharset : UTF8Encoding();
176     auto buffer = decodeURLEscapeSequencesAsData(task.encodedData, encoding);
177
178     buffer.shrinkToFit();
179     task.result.data = SharedBuffer::create(WTFMove(buffer));
180 }
181
182 void decode(const URL& url, const ScheduleContext& scheduleContext, DecodeCompletionHandler&& completionHandler)
183 {
184     ASSERT(url.protocolIsData());
185
186     decodeQueue().dispatch([decodeTask = createDecodeTask(url, scheduleContext, WTFMove(completionHandler))]() mutable {
187         if (decodeTask->isBase64)
188             decodeBase64(*decodeTask);
189         else
190             decodeEscaped(*decodeTask);
191
192 #if HAVE(RUNLOOP_TIMER)
193         DecodingResultDispatcher::dispatch(WTFMove(decodeTask));
194 #else
195         callOnMainThread([decodeTask = WTFMove(decodeTask)] {
196             if (!decodeTask->result.data) {
197                 decodeTask->completionHandler({ });
198                 return;
199             }
200             decodeTask->completionHandler(WTFMove(decodeTask->result));
201         });
202 #endif
203     });
204 }
205
206 }
207 }