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
57namespace WebCore {
58using namespace JSC;
59
60static 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
111static bool canSet(JSValue object, const String& keyPathElement)
112{
113 UNUSED_PARAM(keyPathElement);
114 return object.isObject();
115}
116
117static 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
126JSValue 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
183static const size_t maximumDepth = 2000;
184
185static 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
236static 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
245static 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
257static 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
271static 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
290static 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
307bool 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
342RefPtr<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
360bool 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
376static 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
398JSValue deserializeIDBValueToJSValue(ExecState& state, const IDBValue& value)
399{
400 return deserializeIDBValueToJSValue(state, *state.lexicalGlobalObject(), value);
401}
402
403JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, const IDBValue& value)
404{
405 ASSERT(state);
406 return deserializeIDBValueToJSValue(*state, *globalObject, value);
407}
408
409Ref<IDBKey> scriptValueToIDBKey(ExecState& exec, const JSValue& scriptValue)
410{
411 return createIDBKeyFromValue(exec, scriptValue);
412}
413
414JSC::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
422static 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
458void 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
467Optional<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)
486