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