[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebCore / Modules / encryptedmedia / InitDataRegistry.cpp
1 /*
2  * Copyright (C) 2017 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 "InitDataRegistry.h"
28
29 #if ENABLE(ENCRYPTED_MEDIA)
30
31 #include "ISOProtectionSystemSpecificHeaderBox.h"
32 #include <JavaScriptCore/DataView.h>
33 #include "NotImplemented.h"
34 #include "SharedBuffer.h"
35 #include <wtf/JSONValues.h>
36 #include <wtf/NeverDestroyed.h>
37 #include <wtf/text/Base64.h>
38
39 #if HAVE(FAIRPLAYSTREAMING_CENC_INITDATA)
40 #include "CDMFairPlayStreaming.h"
41 #include "ISOFairPlayStreamingPsshBox.h"
42 #endif
43
44
45 namespace WebCore {
46
47 namespace {
48     const uint32_t kCencMaxBoxSize = 64 * KB;
49     // ContentEncKeyID has this EBML code [47][E2] in WebM,
50     // as per spec the size of the ContentEncKeyID is encoded on 16 bits.
51     // https://matroska.org/technical/specs/index.html#ContentEncKeyID/
52     const uint32_t kWebMMaxContentEncKeyIDSize = 64 * KB; // 2^16
53     const uint32_t kKeyIdsMinKeyIdSizeInBytes = 1;
54     const uint32_t kKeyIdsMaxKeyIdSizeInBytes = 512;
55 }
56
57 static Optional<Vector<Ref<SharedBuffer>>> extractKeyIDsKeyids(const SharedBuffer& buffer)
58 {
59     // 1. Format
60     // https://w3c.github.io/encrypted-media/format-registry/initdata/keyids.html#format
61     if (buffer.size() > std::numeric_limits<unsigned>::max())
62         return WTF::nullopt;
63     String json { buffer.data(), static_cast<unsigned>(buffer.size()) };
64
65     RefPtr<JSON::Value> value;
66     if (!JSON::Value::parseJSON(json, value))
67         return WTF::nullopt;
68
69     RefPtr<JSON::Object> object;
70     if (!value->asObject(object))
71         return WTF::nullopt;
72
73     RefPtr<JSON::Array> kidsArray;
74     if (!object->getArray("kids", kidsArray))
75         return WTF::nullopt;
76
77     Vector<Ref<SharedBuffer>> keyIDs;
78     for (auto& value : *kidsArray) {
79         String keyID;
80         if (!value->asString(keyID))
81             continue;
82
83         Vector<char> keyIDData;
84         if (!WTF::base64URLDecode(keyID, { keyIDData }))
85             continue;
86
87         if (keyIDData.size() < kKeyIdsMinKeyIdSizeInBytes || keyIDData.size() > kKeyIdsMaxKeyIdSizeInBytes)
88             return WTF::nullopt;
89
90         Ref<SharedBuffer> keyIDBuffer = SharedBuffer::create(WTFMove(keyIDData));
91         keyIDs.append(WTFMove(keyIDBuffer));
92     }
93
94     return keyIDs;
95 }
96
97 static RefPtr<SharedBuffer> sanitizeKeyids(const SharedBuffer& buffer)
98 {
99     // 1. Format
100     // https://w3c.github.io/encrypted-media/format-registry/initdata/keyids.html#format
101     auto keyIDBuffer = extractKeyIDsKeyids(buffer);
102     if (!keyIDBuffer)
103         return nullptr;
104
105     auto object = JSON::Object::create();
106     auto kidsArray = JSON::Array::create();
107     for (auto& buffer : keyIDBuffer.value())
108         kidsArray->pushString(WTF::base64URLEncode(buffer->data(), buffer->size()));
109     object->setArray("kids", WTFMove(kidsArray));
110
111     CString jsonData = object->toJSONString().utf8();
112     return SharedBuffer::create(jsonData.data(), jsonData.length());
113 }
114
115 Optional<Vector<std::unique_ptr<ISOProtectionSystemSpecificHeaderBox>>> InitDataRegistry::extractPsshBoxesFromCenc(const SharedBuffer& buffer)
116 {
117     // 4. Common SystemID and PSSH Box Format
118     // https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html#common-system
119     if (buffer.size() >= kCencMaxBoxSize)
120         return WTF::nullopt;
121
122     unsigned offset = 0;
123     Vector<std::unique_ptr<ISOProtectionSystemSpecificHeaderBox>> psshBoxes;
124
125     auto view = JSC::DataView::create(buffer.tryCreateArrayBuffer(), offset, buffer.size());
126     while (auto optionalBoxType = ISOBox::peekBox(view, offset)) {
127         auto& boxTypeName = optionalBoxType.value().first;
128         auto& boxSize = optionalBoxType.value().second;
129
130         if (boxTypeName != ISOProtectionSystemSpecificHeaderBox::boxTypeName() || boxSize > buffer.size())
131             return WTF::nullopt;
132
133         auto systemID = ISOProtectionSystemSpecificHeaderBox::peekSystemID(view, offset);
134 #if HAVE(FAIRPLAYSTREAMING_CENC_INITDATA)
135         if (systemID == ISOFairPlayStreamingPsshBox::fairPlaySystemID()) {
136             auto fpsPssh = makeUnique<ISOFairPlayStreamingPsshBox>();
137             if (!fpsPssh->read(view, offset))
138                 return WTF::nullopt;
139             psshBoxes.append(WTFMove(fpsPssh));
140             continue;
141         }
142 #else
143         UNUSED_PARAM(systemID);
144 #endif
145         auto psshBox = makeUnique<ISOProtectionSystemSpecificHeaderBox>();
146         if (!psshBox->read(view, offset))
147             return WTF::nullopt;
148
149         psshBoxes.append(WTFMove(psshBox));
150     }
151
152     return psshBoxes;
153 }
154
155 Optional<Vector<Ref<SharedBuffer>>> InitDataRegistry::extractKeyIDsCenc(const SharedBuffer& buffer)
156 {
157     Vector<Ref<SharedBuffer>> keyIDs;
158
159     auto psshBoxes = extractPsshBoxesFromCenc(buffer);
160     if (!psshBoxes)
161         return WTF::nullopt;
162
163     for (auto& psshBox : psshBoxes.value()) {
164         ASSERT(psshBox);
165         if (!psshBox)
166             return WTF::nullopt;
167
168 #if HAVE(FAIRPLAYSTREAMING_CENC_INITDATA)
169         if (is<ISOFairPlayStreamingPsshBox>(*psshBox)) {
170             ISOFairPlayStreamingPsshBox& fpsPssh = downcast<ISOFairPlayStreamingPsshBox>(*psshBox);
171
172             FourCC scheme = fpsPssh.initDataBox().info().scheme();
173             if (CDMPrivateFairPlayStreaming::validFairPlayStreamingSchemes().contains(scheme)) {
174                 for (auto request : fpsPssh.initDataBox().requests()) {
175                     auto& keyID = request.requestInfo().keyID();
176                     keyIDs.append(SharedBuffer::create(keyID.data(), keyID.size()));
177                 }
178             }
179         }
180 #endif
181
182         for (auto& value : psshBox->keyIDs())
183             keyIDs.append(SharedBuffer::create(WTFMove(value)));
184     }
185
186     return keyIDs;
187 }
188
189 RefPtr<SharedBuffer> InitDataRegistry::sanitizeCenc(const SharedBuffer& buffer)
190 {
191     // 4. Common SystemID and PSSH Box Format
192     // https://w3c.github.io/encrypted-media/format-registry/initdata/cenc.html#common-system
193     if (!extractKeyIDsCenc(buffer))
194         return nullptr;
195
196     return buffer.copy();
197 }
198
199 static RefPtr<SharedBuffer> sanitizeWebM(const SharedBuffer& buffer)
200 {
201     // Check if the buffer is a valid WebM initData.
202     // The WebM initData is the ContentEncKeyID, so should be less than kWebMMaxContentEncKeyIDSize.
203     if (buffer.isEmpty() || buffer.size() > kWebMMaxContentEncKeyIDSize)
204         return nullptr;
205
206     return buffer.copy();
207 }
208
209 static Optional<Vector<Ref<SharedBuffer>>> extractKeyIDsWebM(const SharedBuffer& buffer)
210 {
211     Vector<Ref<SharedBuffer>> keyIDs;
212     RefPtr<SharedBuffer> sanitizedBuffer = sanitizeWebM(buffer);
213     if (!sanitizedBuffer)
214         return WTF::nullopt;
215
216     // 1. Format
217     // https://w3c.github.io/encrypted-media/format-registry/initdata/webm.html#format
218     keyIDs.append(sanitizedBuffer.releaseNonNull());
219     return keyIDs;
220 }
221
222 InitDataRegistry& InitDataRegistry::shared()
223 {
224     static NeverDestroyed<InitDataRegistry> registry;
225     return registry.get();
226 }
227
228 InitDataRegistry::InitDataRegistry()
229 {
230     registerInitDataType("keyids", { sanitizeKeyids, extractKeyIDsKeyids });
231     registerInitDataType("cenc", { sanitizeCenc, extractKeyIDsCenc });
232     registerInitDataType("webm", { sanitizeWebM, extractKeyIDsWebM });
233 }
234
235 InitDataRegistry::~InitDataRegistry() = default;
236
237 RefPtr<SharedBuffer> InitDataRegistry::sanitizeInitData(const AtomString& initDataType, const SharedBuffer& buffer)
238 {
239     auto iter = m_types.find(initDataType);
240     if (iter == m_types.end() || !iter->value.sanitizeInitData)
241         return nullptr;
242     return iter->value.sanitizeInitData(buffer);
243 }
244
245 Optional<Vector<Ref<SharedBuffer>>> InitDataRegistry::extractKeyIDs(const AtomString& initDataType, const SharedBuffer& buffer)
246 {
247     auto iter = m_types.find(initDataType);
248     if (iter == m_types.end() || !iter->value.sanitizeInitData)
249         return WTF::nullopt;
250     return iter->value.extractKeyIDs(buffer);
251 }
252
253 void InitDataRegistry::registerInitDataType(const AtomString& initDataType, InitDataTypeCallbacks&& callbacks)
254 {
255     ASSERT(!m_types.contains(initDataType));
256     m_types.set(initDataType, WTFMove(callbacks));
257 }
258
259 const AtomString& InitDataRegistry::cencName()
260 {
261     static NeverDestroyed<AtomString> sinf { MAKE_STATIC_STRING_IMPL("cenc") };
262     return sinf;
263 }
264
265 const AtomString& InitDataRegistry::keyidsName()
266 {
267     static NeverDestroyed<AtomString> sinf { MAKE_STATIC_STRING_IMPL("keyids") };
268     return sinf;
269 }
270
271 const AtomString& InitDataRegistry::webmName()
272 {
273     static NeverDestroyed<AtomString> sinf { MAKE_STATIC_STRING_IMPL("webm") };
274     return sinf;
275 }
276
277 }
278
279 #endif // ENABLE(ENCRYPTED_MEDIA)