da106ec3007acaaec1e465a5a5b78c88cb1846cf
[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
32 #include "IDBBindingUtilities.h"
33
34 #include "IDBIndexInfo.h"
35 #include "IDBKey.h"
36 #include "IDBKeyData.h"
37 #include "IDBKeyPath.h"
38 #include "IDBValue.h"
39 #include "IndexKey.h"
40 #include "JSBlob.h"
41 #include "JSDOMBinding.h"
42 #include "JSDOMConvertDate.h"
43 #include "JSDOMConvertNullable.h"
44 #include "JSFile.h"
45 #include "Logging.h"
46 #include "MessagePort.h"
47 #include "ScriptExecutionContext.h"
48 #include "SerializedScriptValue.h"
49 #include "SharedBuffer.h"
50 #include "ThreadSafeDataBuffer.h"
51 #include <JavaScriptCore/ArrayBuffer.h>
52 #include <JavaScriptCore/DateInstance.h>
53 #include <JavaScriptCore/ObjectConstructor.h>
54
55 namespace WebCore {
56 using namespace JSC;
57
58 static bool get(ExecState& exec, JSValue object, const String& keyPathElement, JSValue& result)
59 {
60     if (object.isString() && keyPathElement == "length") {
61         result = jsNumber(asString(object)->length());
62         return true;
63     }
64     if (!object.isObject())
65         return false;
66
67     auto* obj = asObject(object);
68     Identifier identifier = Identifier::fromString(&exec.vm(), keyPathElement);
69     auto& vm = exec.vm();
70     if (obj->inherits<JSArray>(vm) && keyPathElement == "length") {
71         result = obj->get(&exec, identifier);
72         return true;
73     }
74     if (obj->inherits<JSBlob>(vm) && (keyPathElement == "size" || keyPathElement == "type")) {
75         if (keyPathElement == "size") {
76             result = jsNumber(jsCast<JSBlob*>(obj)->wrapped().size());
77             return true;
78         }
79         if (keyPathElement == "type") {
80             result = jsString(&vm, jsCast<JSBlob*>(obj)->wrapped().type());
81             return true;
82         }
83     }
84     if (obj->inherits<JSFile>(vm)) {
85         if (keyPathElement == "name") {
86             result = jsString(&vm, jsCast<JSFile*>(obj)->wrapped().name());
87             return true;
88         }
89         if (keyPathElement == "lastModified") {
90             result = jsNumber(jsCast<JSFile*>(obj)->wrapped().lastModified());
91             return true;
92         }
93         if (keyPathElement == "lastModifiedDate") {
94             result = jsDate(exec, jsCast<JSFile*>(obj)->wrapped().lastModified());
95             return true;
96         }
97     }
98
99     PropertyDescriptor descriptor;
100     if (!obj->getOwnPropertyDescriptor(&exec, identifier, descriptor))
101         return false;
102     if (!descriptor.enumerable())
103         return false;
104
105     result = obj->get(&exec, identifier);
106     return true;
107 }
108
109 static bool canSet(JSValue object, const String& keyPathElement)
110 {
111     UNUSED_PARAM(keyPathElement);
112     return object.isObject();
113 }
114
115 static bool set(ExecState& exec, JSValue& object, const String& keyPathElement, JSValue jsValue)
116 {
117     if (!canSet(object, keyPathElement))
118         return false;
119     Identifier identifier = Identifier::fromString(&exec.vm(), keyPathElement);
120     asObject(object)->putDirect(exec.vm(), identifier, jsValue);
121     return true;
122 }
123
124 JSValue toJS(ExecState& state, JSGlobalObject& globalObject, IDBKey* key)
125 {
126     if (!key) {
127         // This must be undefined, not null.
128         // Spec: http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBKeyRange
129         return jsUndefined();
130     }
131
132     VM& vm = state.vm();
133     Locker<JSLock> locker(vm.apiLock());
134     auto scope = DECLARE_THROW_SCOPE(vm);
135
136     switch (key->type()) {
137     case IndexedDB::KeyType::Array: {
138         auto& inArray = key->array();
139         unsigned size = inArray.size();
140         auto outArray = constructEmptyArray(&state, 0, &globalObject, size);
141         RETURN_IF_EXCEPTION(scope, JSValue());
142         for (size_t i = 0; i < size; ++i) {
143             outArray->putDirectIndex(&state, i, toJS(state, globalObject, inArray.at(i).get()));
144             RETURN_IF_EXCEPTION(scope, JSValue());
145         }
146         return outArray;
147     }
148     case IndexedDB::KeyType::Binary: {
149         auto* data = key->binary().data();
150         if (!data) {
151             ASSERT_NOT_REACHED();
152             return jsNull();
153         }
154
155         auto arrayBuffer = ArrayBuffer::create(data->data(), data->size());
156         Structure* structure = globalObject.arrayBufferStructure(arrayBuffer->sharingMode());
157         if (!structure)
158             return jsNull();
159
160         return JSArrayBuffer::create(state.vm(), structure, WTFMove(arrayBuffer));
161     }
162     case IndexedDB::KeyType::String:
163         return jsStringWithCache(&state, key->string());
164     case IndexedDB::KeyType::Date:
165         // FIXME: This should probably be toJS<IDLDate>(...) as per:
166         // http://w3c.github.io/IndexedDB/#request-convert-a-key-to-a-value
167         return toJS<IDLNullable<IDLDate>>(state, key->date());
168     case IndexedDB::KeyType::Number:
169         return jsNumber(key->number());
170     case IndexedDB::KeyType::Min:
171     case IndexedDB::KeyType::Max:
172     case IndexedDB::KeyType::Invalid:
173         ASSERT_NOT_REACHED();
174         return jsUndefined();
175     }
176
177     ASSERT_NOT_REACHED();
178     return jsUndefined();
179 }
180
181 static const size_t maximumDepth = 2000;
182
183 static RefPtr<IDBKey> createIDBKeyFromValue(ExecState& exec, JSValue value, Vector<JSArray*>& stack)
184 {
185     VM& vm = exec.vm();
186     if (value.isNumber() && !std::isnan(value.toNumber(&exec)))
187         return IDBKey::createNumber(value.toNumber(&exec));
188
189     if (value.isString())
190         return IDBKey::createString(asString(value)->value(&exec));
191
192     if (value.inherits<DateInstance>(vm)) {
193         auto dateValue = valueToDate(exec, value);
194         if (!std::isnan(dateValue))
195             return IDBKey::createDate(dateValue);
196     }
197
198     if (value.isObject()) {
199         JSObject* object = asObject(value);
200         if (auto* array = jsDynamicCast<JSArray*>(vm, object)) {
201             size_t length = array->length();
202
203             if (stack.contains(array))
204                 return nullptr;
205
206             if (stack.size() >= maximumDepth)
207                 return nullptr;
208
209             stack.append(array);
210
211             Vector<RefPtr<IDBKey>> subkeys;
212             for (size_t i = 0; i < length; i++) {
213                 JSValue item = array->getIndex(&exec, i);
214                 RefPtr<IDBKey> subkey = createIDBKeyFromValue(exec, item, stack);
215                 if (!subkey)
216                     subkeys.append(IDBKey::createInvalid());
217                 else
218                     subkeys.append(subkey);
219             }
220
221             stack.removeLast();
222             return IDBKey::createArray(subkeys);
223         }
224
225         if (auto* arrayBuffer = jsDynamicCast<JSArrayBuffer*>(vm, value))
226             return IDBKey::createBinary(*arrayBuffer);
227
228         if (auto* arrayBufferView = jsDynamicCast<JSArrayBufferView*>(vm, value))
229             return IDBKey::createBinary(*arrayBufferView);
230     }
231     return nullptr;
232 }
233
234 static Ref<IDBKey> createIDBKeyFromValue(ExecState& exec, JSValue value)
235 {
236     Vector<JSArray*> stack;
237     RefPtr<IDBKey> key = createIDBKeyFromValue(exec, value, stack);
238     if (key)
239         return *key;
240     return IDBKey::createInvalid();
241 }
242
243 static JSValue getNthValueOnKeyPath(ExecState& exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
244 {
245     JSValue currentValue(rootValue);
246     ASSERT(index <= keyPathElements.size());
247     for (size_t i = 0; i < index; i++) {
248         JSValue parentValue(currentValue);
249         if (!get(exec, parentValue, keyPathElements[i], currentValue))
250             return jsUndefined();
251     }
252     return currentValue;
253 }
254
255 static RefPtr<IDBKey> internalCreateIDBKeyFromScriptValueAndKeyPath(ExecState& exec, const JSValue& value, const String& keyPath)
256 {
257     Vector<String> keyPathElements;
258     IDBKeyPathParseError error;
259     IDBParseKeyPath(keyPath, keyPathElements, error);
260     ASSERT(error == IDBKeyPathParseError::None);
261
262     JSValue jsValue = value;
263     jsValue = getNthValueOnKeyPath(exec, jsValue, keyPathElements, keyPathElements.size());
264     if (jsValue.isUndefined())
265         return nullptr;
266     return createIDBKeyFromValue(exec, jsValue);
267 }
268
269 static JSValue ensureNthValueOnKeyPath(ExecState& exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
270 {
271     JSValue currentValue(rootValue);
272
273     ASSERT(index <= keyPathElements.size());
274     for (size_t i = 0; i < index; i++) {
275         JSValue parentValue(currentValue);
276         const String& keyPathElement = keyPathElements[i];
277         if (!get(exec, parentValue, keyPathElement, currentValue)) {
278             JSObject* object = constructEmptyObject(&exec);
279             if (!set(exec, parentValue, keyPathElement, JSValue(object)))
280                 return jsUndefined();
281             currentValue = JSValue(object);
282         }
283     }
284
285     return currentValue;
286 }
287
288 static bool canInjectNthValueOnKeyPath(ExecState& exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
289 {
290     if (!rootValue.isObject())
291         return false;
292
293     JSValue currentValue(rootValue);
294
295     ASSERT(index <= keyPathElements.size());
296     for (size_t i = 0; i <= index; ++i) {
297         JSValue parentValue(currentValue);
298         const String& keyPathElement = keyPathElements[i];
299         if (!get(exec, parentValue, keyPathElement, currentValue))
300             return canSet(parentValue, keyPathElement);
301     }
302     return true;
303 }
304
305 bool injectIDBKeyIntoScriptValue(ExecState& exec, const IDBKeyData& keyData, JSValue value, const IDBKeyPath& keyPath)
306 {
307     LOG(IndexedDB, "injectIDBKeyIntoScriptValue");
308
309     ASSERT(WTF::holds_alternative<String>(keyPath));
310
311     Vector<String> keyPathElements;
312     IDBKeyPathParseError error;
313     IDBParseKeyPath(WTF::get<String>(keyPath), keyPathElements, error);
314     ASSERT(error == IDBKeyPathParseError::None);
315
316     if (keyPathElements.isEmpty())
317         return false;
318
319     JSValue parent = ensureNthValueOnKeyPath(exec, value, keyPathElements, keyPathElements.size() - 1);
320     if (parent.isUndefined())
321         return false;
322
323     auto key = keyData.maybeCreateIDBKey();
324     if (!key)
325         return false;
326
327     if (!set(exec, parent, keyPathElements.last(), toJS(exec, *exec.lexicalGlobalObject(), key.get())))
328         return false;
329
330     return true;
331 }
332
333
334 RefPtr<IDBKey> maybeCreateIDBKeyFromScriptValueAndKeyPath(ExecState& exec, const JSValue& value, const IDBKeyPath& keyPath)
335 {
336     if (WTF::holds_alternative<Vector<String>>(keyPath)) {
337         auto& array = WTF::get<Vector<String>>(keyPath);
338         Vector<RefPtr<IDBKey>> result;
339         result.reserveInitialCapacity(array.size());
340         for (auto& string : array) {
341             RefPtr<IDBKey> key = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, string);
342             if (!key)
343                 return nullptr;
344             result.uncheckedAppend(WTFMove(key));
345         }
346         return IDBKey::createArray(WTFMove(result));
347     }
348
349     return internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, WTF::get<String>(keyPath));
350 }
351
352 bool canInjectIDBKeyIntoScriptValue(ExecState& exec, const JSValue& scriptValue, const IDBKeyPath& keyPath)
353 {
354     LOG(StorageAPI, "canInjectIDBKeyIntoScriptValue");
355
356     ASSERT(WTF::holds_alternative<String>(keyPath));
357     Vector<String> keyPathElements;
358     IDBKeyPathParseError error;
359     IDBParseKeyPath(WTF::get<String>(keyPath), keyPathElements, error);
360     ASSERT(error == IDBKeyPathParseError::None);
361
362     if (!keyPathElements.size())
363         return false;
364
365     return canInjectNthValueOnKeyPath(exec, scriptValue, keyPathElements, keyPathElements.size() - 1);
366 }
367
368 static JSValue deserializeIDBValueToJSValue(ExecState& state, JSC::JSGlobalObject& globalObject, const IDBValue& value)
369 {
370     // FIXME: I think it's peculiar to use undefined to mean "null data" and null to mean "empty data".
371     // But I am not changing this at the moment because at least some callers are specifically checking isUndefined.
372
373     if (!value.data().data())
374         return jsUndefined();
375
376     auto& data = *value.data().data();
377     if (data.isEmpty())
378         return jsNull();
379
380     auto serializedValue = SerializedScriptValue::createFromWireBytes(Vector<uint8_t>(data));
381
382     state.vm().apiLock().lock();
383     Vector<RefPtr<MessagePort>> messagePorts;
384     JSValue result = serializedValue->deserialize(state, &globalObject, messagePorts, value.blobURLs(), value.sessionID(), value.blobFilePaths(), SerializationErrorMode::NonThrowing);
385     state.vm().apiLock().unlock();
386
387     return result;
388 }
389
390 JSValue deserializeIDBValueToJSValue(ExecState& state, const IDBValue& value)
391 {
392     return deserializeIDBValueToJSValue(state, *state.lexicalGlobalObject(), value);
393 }
394
395 JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, const IDBValue& value)
396 {
397     ASSERT(state);
398     return deserializeIDBValueToJSValue(*state, *globalObject, value);
399 }
400
401 Ref<IDBKey> scriptValueToIDBKey(ExecState& exec, const JSValue& scriptValue)
402 {
403     return createIDBKeyFromValue(exec, scriptValue);
404 }
405
406 JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, const IDBKeyData& keyData)
407 {
408     ASSERT(state);
409     ASSERT(globalObject);
410
411     return toJS(*state, *globalObject, keyData.maybeCreateIDBKey().get());
412 }
413
414 static Vector<IDBKeyData> createKeyPathArray(ExecState& exec, JSValue value, const IDBIndexInfo& info)
415 {
416     auto visitor = WTF::makeVisitor([&](const String& string) -> Vector<IDBKeyData> {
417         auto idbKey = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, string);
418         if (!idbKey)
419             return { };
420
421         Vector<IDBKeyData> keys;
422         if (info.multiEntry() && idbKey->type() == IndexedDB::Array) {
423             for (auto& key : idbKey->array())
424                 keys.append(key.get());
425         } else
426             keys.append(idbKey.get());
427         return keys;
428     }, [&](const Vector<String>& vector) -> Vector<IDBKeyData> {
429         Vector<IDBKeyData> keys;
430         for (auto& entry : vector) {
431             auto key = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, entry);
432             if (!key || !key->isValid())
433                 return { };
434             keys.append(key.get());
435         }
436         return keys;
437     });
438
439     return WTF::visit(visitor, info.keyPath());
440 }
441
442 void generateIndexKeyForValue(ExecState& exec, const IDBIndexInfo& info, JSValue value, IndexKey& outKey)
443 {
444     auto keyDatas = createKeyPathArray(exec, value, info);
445
446     if (keyDatas.isEmpty())
447         return;
448
449     outKey = IndexKey(WTFMove(keyDatas));
450 }
451
452 } // namespace WebCore
453
454 #endif // ENABLE(INDEXED_DATABASE)