Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / Modules / encryptedmedia / legacy / LegacyCDMSessionClearKey.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 "LegacyCDMSessionClearKey.h"
28
29 #include "JSMainThreadExecState.h"
30 #include "Logging.h"
31 #include "TextEncoding.h"
32 #include "WebKitMediaKeyError.h"
33 #include <runtime/JSGlobalObject.h>
34 #include <runtime/JSLock.h>
35 #include <runtime/JSONObject.h>
36 #include <runtime/VM.h>
37 #include <wtf/UUID.h>
38 #include <wtf/text/Base64.h>
39
40 #if ENABLE(LEGACY_ENCRYPTED_MEDIA)
41
42 using namespace JSC;
43
44 namespace WebCore {
45
46 static VM& clearKeyVM()
47 {
48     static VM& vm = VM::create().leakRef();
49     return vm;
50 }
51
52 CDMSessionClearKey::CDMSessionClearKey(CDMSessionClient* client)
53     : m_client(client)
54     , m_sessionId(createCanonicalUUIDString())
55 {
56 }
57
58 CDMSessionClearKey::~CDMSessionClearKey()
59 {
60 }
61
62 RefPtr<Uint8Array> CDMSessionClearKey::generateKeyRequest(const String& mimeType, Uint8Array* initData, String& destinationURL, unsigned short& errorCode, uint32_t& systemCode)
63 {
64     UNUSED_PARAM(mimeType);
65     UNUSED_PARAM(destinationURL);
66     UNUSED_PARAM(systemCode);
67
68     if (!initData) {
69         errorCode = WebKitMediaKeyError::MEDIA_KEYERR_CLIENT;
70         return nullptr;
71     }
72     m_initData = initData;
73
74     bool sawError = false;
75     String keyID = UTF8Encoding().decode(reinterpret_cast_ptr<char*>(m_initData->baseAddress()), m_initData->byteLength(), true, sawError);
76     if (sawError) {
77         errorCode = WebKitMediaKeyError::MEDIA_KEYERR_CLIENT;
78         return nullptr;
79     }
80
81     return initData;
82 }
83
84 void CDMSessionClearKey::releaseKeys()
85 {
86     m_cachedKeys.clear();
87 }
88
89 bool CDMSessionClearKey::update(Uint8Array* rawKeysData, RefPtr<Uint8Array>& nextMessage, unsigned short& errorCode, uint32_t& systemCode)
90 {
91     UNUSED_PARAM(nextMessage);
92     UNUSED_PARAM(systemCode);
93     ASSERT(rawKeysData);
94
95     do {
96         auto rawKeysString = String::fromUTF8(rawKeysData->data(), rawKeysData->length());
97         if (rawKeysString.isEmpty())  {
98             LOG(Media, "CDMSessionClearKey::update(%p) - failed: empty message", this);
99             continue;
100         }
101
102         auto& vm = clearKeyVM();
103         JSLockHolder lock(vm);
104         auto scope = DECLARE_THROW_SCOPE(vm);
105         auto* globalObject = JSGlobalObject::create(vm, JSGlobalObject::createStructure(vm, jsNull()));
106         auto& state = *globalObject->globalExec();
107
108         auto keysDataValue = JSONParse(&state, rawKeysString);
109         if (scope.exception() || !keysDataValue.isObject()) {
110             LOG(Media, "CDMSessionClearKey::update(%p) - failed: invalid JSON", this);
111             break;
112         }
113
114         auto keysArrayValue = asObject(keysDataValue)->get(&state, Identifier::fromString(&state, "keys"));
115         if (scope.exception() || !isJSArray(keysArrayValue)) {
116             LOG(Media, "CDMSessionClearKey::update(%p) - failed: keys array missing or empty", this);
117             break;
118         }
119
120         auto keysArray = asArray(keysArrayValue);
121         auto length = keysArray->length();
122         if (!length) {
123             LOG(Media, "CDMSessionClearKey::update(%p) - failed: keys array missing or empty", this);
124             break;
125         }
126
127         bool foundValidKey = false;
128         for (unsigned i = 0; i < length; ++i) {
129             auto keyValue = keysArray->getIndex(&state, i);
130
131             if (scope.exception() || !keyValue.isObject()) {
132                 LOG(Media, "CDMSessionClearKey::update(%p) - failed: null keyDictionary", this);
133                 continue;
134             }
135
136             auto keyObject = asObject(keyValue);
137
138             auto getStringProperty = [&scope, &state, &keyObject](const char* name) -> String {
139                 auto value = keyObject->get(&state, Identifier::fromString(&state, name));
140                 if (scope.exception() || !value.isString())
141                     return { };
142
143                 auto string = asString(value)->value(&state);
144                 if (scope.exception())
145                     return { };
146                 
147                 return string;
148             };
149
150             auto algorithm = getStringProperty("alg");
151             if (!equalLettersIgnoringASCIICase(algorithm, "a128kw")) {
152                 LOG(Media, "CDMSessionClearKey::update(%p) - failed: algorithm unsupported", this);
153                 continue;
154             }
155
156             auto keyType = getStringProperty("kty");
157             if (!equalLettersIgnoringASCIICase(keyType, "oct")) {
158                 LOG(Media, "CDMSessionClearKey::update(%p) - failed: keyType unsupported", this);
159                 continue;
160             }
161
162             auto keyId = getStringProperty("kid");
163             if (keyId.isEmpty()) {
164                 LOG(Media, "CDMSessionClearKey::update(%p) - failed: keyId missing or empty", this);
165                 continue;
166             }
167
168             auto rawKeyData = getStringProperty("k");
169             if (rawKeyData.isEmpty())  {
170                 LOG(Media, "CDMSessionClearKey::update(%p) - failed: key missing or empty", this);
171                 continue;
172             }
173
174             Vector<uint8_t> keyData;
175             if (!base64Decode(rawKeyData, keyData) || keyData.isEmpty()) {
176                 LOG(Media, "CDMSessionClearKey::update(%p) - failed: unable to base64 decode key", this);
177                 continue;
178             }
179
180             m_cachedKeys.set(keyId, WTFMove(keyData));
181             foundValidKey = true;
182         }
183
184         if (foundValidKey)
185             return true;
186
187     } while (false);
188
189     errorCode = WebKitMediaKeyError::MEDIA_KEYERR_CLIENT;
190     return false;
191 }
192
193 RefPtr<ArrayBuffer> CDMSessionClearKey::cachedKeyForKeyID(const String& keyId) const
194 {
195     if (!m_cachedKeys.contains(keyId))
196         return nullptr;
197
198     auto keyData = m_cachedKeys.get(keyId);
199     RefPtr<Uint8Array> keyDataArray = Uint8Array::create(keyData.data(), keyData.size());
200     return keyDataArray->unsharedBuffer();
201 }
202
203 }
204
205 #endif