[WTF] Import std::optional reference implementation as WTF::Optional
[WebKit-https.git] / Source / WebCore / bindings / js / IDBBindingUtilities.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2012 Michael Pruett <michael@68k.org>
4  * Copyright (C) 2014, 2015, 2016 Apple Inc. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29
30 #if ENABLE(INDEXED_DATABASE)
31 #include "IDBBindingUtilities.h"
32
33 #include "IDBIndexInfo.h"
34 #include "IDBKey.h"
35 #include "IDBKeyData.h"
36 #include "IDBKeyPath.h"
37 #include "IDBValue.h"
38 #include "IndexKey.h"
39 #include "JSDOMBinding.h"
40 #include "JSDOMConvert.h"
41 #include "JSDOMStringList.h"
42 #include "Logging.h"
43 #include "MessagePort.h"
44 #include "ScriptExecutionContext.h"
45 #include "SerializedScriptValue.h"
46 #include "SharedBuffer.h"
47 #include "ThreadSafeDataBuffer.h"
48 #include <runtime/ArrayBuffer.h>
49 #include <runtime/DateInstance.h>
50 #include <runtime/ObjectConstructor.h>
51
52 using namespace JSC;
53
54 namespace WebCore {
55
56 static bool get(ExecState& exec, JSValue object, const String& keyPathElement, JSValue& result)
57 {
58     if (object.isString() && keyPathElement == "length") {
59         result = jsNumber(object.toString(&exec)->length());
60         return true;
61     }
62     if (!object.isObject())
63         return false;
64     Identifier identifier = Identifier::fromString(&exec.vm(), keyPathElement);
65     if (!asObject(object)->hasProperty(&exec, identifier))
66         return false;
67     result = asObject(object)->get(&exec, identifier);
68     return true;
69 }
70
71 static bool canSet(JSValue object, const String& keyPathElement)
72 {
73     UNUSED_PARAM(keyPathElement);
74     return object.isObject();
75 }
76
77 static bool set(ExecState& exec, JSValue& object, const String& keyPathElement, JSValue jsValue)
78 {
79     if (!canSet(object, keyPathElement))
80         return false;
81     Identifier identifier = Identifier::fromString(&exec.vm(), keyPathElement);
82     asObject(object)->putDirect(exec.vm(), identifier, jsValue);
83     return true;
84 }
85
86 JSValue toJS(ExecState& state, JSGlobalObject& globalObject, IDBKey* key)
87 {
88     if (!key) {
89         // This must be undefined, not null.
90         // Spec: http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBKeyRange
91         return jsUndefined();
92     }
93
94     VM& vm = state.vm();
95     Locker<JSLock> locker(vm.apiLock());
96     auto scope = DECLARE_THROW_SCOPE(vm);
97
98     switch (key->type()) {
99     case KeyType::Array: {
100         auto& inArray = key->array();
101         unsigned size = inArray.size();
102         auto outArray = constructEmptyArray(&state, 0, &globalObject, size);
103         RETURN_IF_EXCEPTION(scope, JSValue());
104         for (size_t i = 0; i < size; ++i)
105             outArray->putDirectIndex(&state, i, toJS(state, globalObject, inArray.at(i).get()));
106         return outArray;
107     }
108     case KeyType::Binary: {
109         auto* data = key->binary().data();
110         if (!data) {
111             ASSERT_NOT_REACHED();
112             return jsNull();
113         }
114
115         auto arrayBuffer = ArrayBuffer::create(data->data(), data->size());
116         Structure* structure = globalObject.arrayBufferStructure(arrayBuffer->sharingMode());
117         if (!structure)
118             return jsNull();
119
120         return JSArrayBuffer::create(state.vm(), structure, WTFMove(arrayBuffer));
121     }
122     case KeyType::String:
123         return jsStringWithCache(&state, key->string());
124     case KeyType::Date:
125         // FIXME: This should probably be toJS<IDLDate>(...) as per:
126         // http://w3c.github.io/IndexedDB/#request-convert-a-key-to-a-value
127         return toJS<IDLNullable<IDLDate>>(state, key->date());
128     case KeyType::Number:
129         return jsNumber(key->number());
130     case KeyType::Min:
131     case KeyType::Max:
132     case KeyType::Invalid:
133         ASSERT_NOT_REACHED();
134         return jsUndefined();
135     }
136
137     ASSERT_NOT_REACHED();
138     return jsUndefined();
139 }
140
141 static const size_t maximumDepth = 2000;
142
143 static RefPtr<IDBKey> createIDBKeyFromValue(ExecState& exec, JSValue value, Vector<JSArray*>& stack)
144 {
145     if (value.isNumber() && !std::isnan(value.toNumber(&exec)))
146         return IDBKey::createNumber(value.toNumber(&exec));
147
148     if (value.isString())
149         return IDBKey::createString(value.toString(&exec)->value(&exec));
150
151     if (value.inherits(DateInstance::info()) && !std::isnan(valueToDate(&exec, value)))
152         return IDBKey::createDate(valueToDate(&exec, value));
153
154     if (value.isObject()) {
155         JSObject* object = asObject(value);
156         if (isJSArray(object) || object->inherits(JSArray::info())) {
157             JSArray* array = asArray(object);
158             size_t length = array->length();
159
160             if (stack.contains(array))
161                 return nullptr;
162
163             if (stack.size() >= maximumDepth)
164                 return nullptr;
165
166             stack.append(array);
167
168             Vector<RefPtr<IDBKey>> subkeys;
169             for (size_t i = 0; i < length; i++) {
170                 JSValue item = array->getIndex(&exec, i);
171                 RefPtr<IDBKey> subkey = createIDBKeyFromValue(exec, item, stack);
172                 if (!subkey)
173                     subkeys.append(IDBKey::createInvalid());
174                 else
175                     subkeys.append(subkey);
176             }
177
178             stack.removeLast();
179             return IDBKey::createArray(subkeys);
180         }
181
182         if (auto* arrayBuffer = jsDynamicCast<JSArrayBuffer*>(value))
183             return IDBKey::createBinary(*arrayBuffer);
184
185         if (auto* arrayBufferView = jsDynamicCast<JSArrayBufferView*>(value))
186             return IDBKey::createBinary(*arrayBufferView);
187     }
188     return nullptr;
189 }
190
191 static Ref<IDBKey> createIDBKeyFromValue(ExecState& exec, JSValue value)
192 {
193     Vector<JSArray*> stack;
194     RefPtr<IDBKey> key = createIDBKeyFromValue(exec, value, stack);
195     if (key)
196         return *key;
197     return IDBKey::createInvalid();
198 }
199
200 static JSValue getNthValueOnKeyPath(ExecState& exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
201 {
202     JSValue currentValue(rootValue);
203     ASSERT(index <= keyPathElements.size());
204     for (size_t i = 0; i < index; i++) {
205         JSValue parentValue(currentValue);
206         if (!get(exec, parentValue, keyPathElements[i], currentValue))
207             return jsUndefined();
208     }
209     return currentValue;
210 }
211
212 static RefPtr<IDBKey> internalCreateIDBKeyFromScriptValueAndKeyPath(ExecState& exec, const JSValue& value, const String& keyPath)
213 {
214     Vector<String> keyPathElements;
215     IDBKeyPathParseError error;
216     IDBParseKeyPath(keyPath, keyPathElements, error);
217     ASSERT(error == IDBKeyPathParseError::None);
218
219     JSValue jsValue = value;
220     jsValue = getNthValueOnKeyPath(exec, jsValue, keyPathElements, keyPathElements.size());
221     if (jsValue.isUndefined())
222         return nullptr;
223     return createIDBKeyFromValue(exec, jsValue);
224 }
225
226 static JSValue ensureNthValueOnKeyPath(ExecState& exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
227 {
228     JSValue currentValue(rootValue);
229
230     ASSERT(index <= keyPathElements.size());
231     for (size_t i = 0; i < index; i++) {
232         JSValue parentValue(currentValue);
233         const String& keyPathElement = keyPathElements[i];
234         if (!get(exec, parentValue, keyPathElement, currentValue)) {
235             JSObject* object = constructEmptyObject(&exec);
236             if (!set(exec, parentValue, keyPathElement, JSValue(object)))
237                 return jsUndefined();
238             currentValue = JSValue(object);
239         }
240     }
241
242     return currentValue;
243 }
244
245 static bool canInjectNthValueOnKeyPath(ExecState& exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
246 {
247     if (!rootValue.isObject())
248         return false;
249
250     JSValue currentValue(rootValue);
251
252     ASSERT(index <= keyPathElements.size());
253     for (size_t i = 0; i < index; ++i) {
254         JSValue parentValue(currentValue);
255         const String& keyPathElement = keyPathElements[i];
256         if (!get(exec, parentValue, keyPathElement, currentValue))
257             return canSet(parentValue, keyPathElement);
258     }
259     return true;
260 }
261
262 bool injectIDBKeyIntoScriptValue(ExecState& exec, const IDBKeyData& keyData, JSValue value, const IDBKeyPath& keyPath)
263 {
264     LOG(IndexedDB, "injectIDBKeyIntoScriptValue");
265
266     ASSERT(WTF::holds_alternative<String>(keyPath));
267
268     Vector<String> keyPathElements;
269     IDBKeyPathParseError error;
270     IDBParseKeyPath(WTF::get<String>(keyPath), keyPathElements, error);
271     ASSERT(error == IDBKeyPathParseError::None);
272
273     if (keyPathElements.isEmpty())
274         return false;
275
276     JSValue parent = ensureNthValueOnKeyPath(exec, value, keyPathElements, keyPathElements.size() - 1);
277     if (parent.isUndefined())
278         return false;
279
280     auto key = keyData.maybeCreateIDBKey();
281     if (!key)
282         return false;
283
284     if (!set(exec, parent, keyPathElements.last(), toJS(exec, *exec.lexicalGlobalObject(), key.get())))
285         return false;
286
287     return true;
288 }
289
290
291 RefPtr<IDBKey> maybeCreateIDBKeyFromScriptValueAndKeyPath(ExecState& exec, const JSValue& value, const IDBKeyPath& keyPath)
292 {
293     if (WTF::holds_alternative<Vector<String>>(keyPath)) {
294         auto& array = WTF::get<Vector<String>>(keyPath);
295         Vector<RefPtr<IDBKey>> result;
296         result.reserveInitialCapacity(array.size());
297         for (auto& string : array) {
298             RefPtr<IDBKey> key = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, string);
299             if (!key)
300                 return nullptr;
301             result.uncheckedAppend(WTFMove(key));
302         }
303         return IDBKey::createArray(WTFMove(result));
304     }
305
306     return internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, WTF::get<String>(keyPath));
307 }
308
309 bool canInjectIDBKeyIntoScriptValue(ExecState& exec, const JSValue& scriptValue, const IDBKeyPath& keyPath)
310 {
311     LOG(StorageAPI, "canInjectIDBKeyIntoScriptValue");
312
313     ASSERT(WTF::holds_alternative<String>(keyPath));
314     Vector<String> keyPathElements;
315     IDBKeyPathParseError error;
316     IDBParseKeyPath(WTF::get<String>(keyPath), keyPathElements, error);
317     ASSERT(error == IDBKeyPathParseError::None);
318
319     if (!keyPathElements.size())
320         return false;
321
322     return canInjectNthValueOnKeyPath(exec, scriptValue, keyPathElements, keyPathElements.size() - 1);
323 }
324
325 static JSValue deserializeIDBValueToJSValue(ExecState& state, JSC::JSGlobalObject& globalObject, const IDBValue& value)
326 {
327     // FIXME: I think it's peculiar to use undefined to mean "null data" and null to mean "empty data".
328     // But I am not changing this at the moment because at least some callers are specifically checking isUndefined.
329
330     if (!value.data().data())
331         return jsUndefined();
332
333     auto& data = *value.data().data();
334     if (data.isEmpty())
335         return jsNull();
336
337     auto serializedValue = SerializedScriptValue::createFromWireBytes(Vector<uint8_t>(data));
338
339     state.vm().apiLock().lock();
340     Vector<RefPtr<MessagePort>> messagePorts;
341     JSValue result = serializedValue->deserialize(state, &globalObject, messagePorts, value.blobURLs(), value.blobFilePaths(), NonThrowing);
342     state.vm().apiLock().unlock();
343
344     return result;
345 }
346
347 JSValue deserializeIDBValueToJSValue(ExecState& state, const IDBValue& value)
348 {
349     return deserializeIDBValueToJSValue(state, *state.lexicalGlobalObject(), value);
350 }
351
352 JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, const IDBValue& value)
353 {
354     ASSERT(state);
355     return deserializeIDBValueToJSValue(*state, *globalObject, value);
356 }
357
358 Ref<IDBKey> scriptValueToIDBKey(ExecState& exec, const JSValue& scriptValue)
359 {
360     return createIDBKeyFromValue(exec, scriptValue);
361 }
362
363 JSC::JSValue idbKeyDataToScriptValue(JSC::ExecState& exec, const IDBKeyData& keyData)
364 {
365     RefPtr<IDBKey> key = keyData.maybeCreateIDBKey();
366     return toJS(exec, *exec.lexicalGlobalObject(), key.get());
367 }
368
369 JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, const IDBKeyData& keyData)
370 {
371     ASSERT(state);
372     ASSERT(globalObject);
373
374     return toJS(*state, *globalObject, keyData.maybeCreateIDBKey().get());
375 }
376
377 static Vector<IDBKeyData> createKeyPathArray(ExecState& exec, JSValue value, const IDBIndexInfo& info)
378 {
379     auto visitor = WTF::makeVisitor([&](const String& string) -> Vector<IDBKeyData> {
380         auto idbKey = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, string);
381         if (!idbKey)
382             return { };
383
384         Vector<IDBKeyData> keys;
385         if (info.multiEntry() && idbKey->type() == IndexedDB::Array) {
386             for (auto& key : idbKey->array())
387                 keys.append(key.get());
388         } else
389             keys.append(idbKey.get());
390         return keys;
391     }, [&](const Vector<String>& vector) -> Vector<IDBKeyData> {
392         Vector<IDBKeyData> keys;
393         for (auto& entry : vector) {
394             auto key = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, entry);
395             if (!key)
396                 return { };
397             keys.append(key.get());
398         }
399         return keys;
400     });
401
402     return WTF::visit(visitor, info.keyPath());
403 }
404
405 void generateIndexKeyForValue(ExecState& exec, const IDBIndexInfo& info, JSValue value, IndexKey& outKey)
406 {
407     auto keyDatas = createKeyPathArray(exec, value, info);
408
409     if (keyDatas.isEmpty())
410         return;
411
412     outKey = IndexKey(WTFMove(keyDatas));
413 }
414
415 JSValue toJS(ExecState& state, JSDOMGlobalObject& globalObject, const std::optional<IDBKeyPath>& keyPath)
416 {
417     if (!keyPath)
418         return jsNull();
419
420     auto visitor = WTF::makeVisitor([&](const String& string) -> JSValue {
421         return toJS<IDLDOMString>(state, globalObject, string);
422     }, [&](const Vector<String>& vector) -> JSValue {
423         return toJS<IDLSequence<IDLDOMString>>(state, globalObject, vector);
424     });
425     return WTF::visit(visitor, keyPath.value());
426 }
427
428 } // namespace WebCore
429
430 #endif // ENABLE(INDEXED_DATABASE)