1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Michael Pruett <michael@68k.org>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28
29#if ENABLE(INDEXED_DATABASE)
30#include "IDBBindingUtilities.h"
31
32#include "DOMRequestState.h"
33#include "IDBIndexMetadata.h"
34#include "IDBKey.h"
35#include "IDBKeyData.h"
36#include "IDBKeyPath.h"
37#include "JSDOMBinding.h"
38#include "Logging.h"
39#include "SharedBuffer.h"
40
41#include <runtime/DateInstance.h>
42#include <runtime/ObjectConstructor.h>
43
44using namespace JSC;
45
46namespace WebCore {
47
48static bool get(ExecState* exec, JSValue object, const String& keyPathElement, JSValue& result)
49{
50    if (object.isString() && keyPathElement == "length") {
51        result = jsNumber(object.toString(exec)->length());
52        return true;
53    }
54    if (!object.isObject())
55        return false;
56    Identifier identifier(&exec->vm(), keyPathElement.utf8().data());
57    if (!asObject(object)->hasProperty(exec, identifier))
58        return false;
59    result = asObject(object)->get(exec, identifier);
60    return true;
61}
62
63static bool canSet(JSValue object, const String& keyPathElement)
64{
65    UNUSED_PARAM(keyPathElement);
66    return object.isObject();
67}
68
69static bool set(ExecState* exec, JSValue& object, const String& keyPathElement, JSValue jsValue)
70{
71    if (!canSet(object, keyPathElement))
72        return false;
73    Identifier identifier(&exec->vm(), keyPathElement.utf8().data());
74    asObject(object)->putDirect(exec->vm(), identifier, jsValue);
75    return true;
76}
77
78static JSValue idbKeyToJSValue(ExecState* exec, JSDOMGlobalObject* globalObject, IDBKey* key)
79{
80    if (!key) {
81        // This should be undefined, not null.
82        // Spec: http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBKeyRange
83        return jsUndefined();
84    }
85
86    switch (key->type()) {
87    case IDBKey::ArrayType:
88        {
89            const IDBKey::KeyArray& inArray = key->array();
90            size_t size = inArray.size();
91            JSArray* outArray = constructEmptyArray(exec, 0, globalObject, size);
92            for (size_t i = 0; i < size; ++i) {
93                IDBKey* arrayKey = inArray.at(i).get();
94                outArray->putDirectIndex(exec, i, idbKeyToJSValue(exec, globalObject, arrayKey));
95            }
96            return JSValue(outArray);
97        }
98    case IDBKey::StringType:
99        return jsStringWithCache(exec, key->string());
100    case IDBKey::DateType:
101        return jsDateOrNull(exec, key->date());
102    case IDBKey::NumberType:
103        return jsNumber(key->number());
104    case IDBKey::MinType:
105    case IDBKey::MaxType:
106    case IDBKey::InvalidType:
107        ASSERT_NOT_REACHED();
108        return jsUndefined();
109    }
110
111    ASSERT_NOT_REACHED();
112    return jsUndefined();
113}
114
115static const size_t maximumDepth = 2000;
116
117static PassRefPtr<IDBKey> createIDBKeyFromValue(ExecState* exec, JSValue value, Vector<JSArray*>& stack)
118{
119    if (value.isNumber() && !std::isnan(value.toNumber(exec)))
120        return IDBKey::createNumber(value.toNumber(exec));
121    if (value.isString())
122        return IDBKey::createString(value.toString(exec)->value(exec));
123    if (value.inherits(DateInstance::info()) && !std::isnan(valueToDate(exec, value)))
124        return IDBKey::createDate(valueToDate(exec, value));
125    if (value.isObject()) {
126        JSObject* object = asObject(value);
127        if (isJSArray(object) || object->inherits(JSArray::info())) {
128            JSArray* array = asArray(object);
129            size_t length = array->length();
130
131            if (stack.contains(array))
132                return 0;
133            if (stack.size() >= maximumDepth)
134                return 0;
135            stack.append(array);
136
137            IDBKey::KeyArray subkeys;
138            for (size_t i = 0; i < length; i++) {
139                JSValue item = array->getIndex(exec, i);
140                RefPtr<IDBKey> subkey = createIDBKeyFromValue(exec, item, stack);
141                if (!subkey)
142                    subkeys.append(IDBKey::createInvalid());
143                else
144                    subkeys.append(subkey);
145            }
146
147            stack.removeLast();
148            return IDBKey::createArray(subkeys);
149        }
150    }
151    return 0;
152}
153
154static PassRefPtr<IDBKey> createIDBKeyFromValue(ExecState* exec, JSValue value)
155{
156    Vector<JSArray*> stack;
157    RefPtr<IDBKey> key = createIDBKeyFromValue(exec, value, stack);
158    if (key)
159        return key;
160    return IDBKey::createInvalid();
161}
162
163IDBKeyPath idbKeyPathFromValue(ExecState* exec, JSValue keyPathValue)
164{
165    IDBKeyPath keyPath;
166    if (isJSArray(keyPathValue))
167        keyPath = IDBKeyPath(toNativeArray<String>(exec, keyPathValue));
168    else
169        keyPath = IDBKeyPath(keyPathValue.toString(exec)->value(exec));
170    return keyPath;
171}
172
173static JSValue getNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
174{
175    JSValue currentValue(rootValue);
176    ASSERT(index <= keyPathElements.size());
177    for (size_t i = 0; i < index; i++) {
178        JSValue parentValue(currentValue);
179        if (!get(exec, parentValue, keyPathElements[i], currentValue))
180            return jsUndefined();
181    }
182    return currentValue;
183}
184
185static PassRefPtr<IDBKey> internalCreateIDBKeyFromScriptValueAndKeyPath(ExecState* exec, const Deprecated::ScriptValue& value, const String& keyPath)
186{
187    Vector<String> keyPathElements;
188    IDBKeyPathParseError error;
189    IDBParseKeyPath(keyPath, keyPathElements, error);
190    ASSERT(error == IDBKeyPathParseErrorNone);
191
192    JSValue jsValue = value.jsValue();
193    jsValue = getNthValueOnKeyPath(exec, jsValue, keyPathElements, keyPathElements.size());
194    if (jsValue.isUndefined())
195        return 0;
196    return createIDBKeyFromValue(exec, jsValue);
197}
198
199static JSValue ensureNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
200{
201    JSValue currentValue(rootValue);
202
203    ASSERT(index <= keyPathElements.size());
204    for (size_t i = 0; i < index; i++) {
205        JSValue parentValue(currentValue);
206        const String& keyPathElement = keyPathElements[i];
207        if (!get(exec, parentValue, keyPathElement, currentValue)) {
208            JSObject* object = constructEmptyObject(exec);
209            if (!set(exec, parentValue, keyPathElement, JSValue(object)))
210                return jsUndefined();
211            currentValue = JSValue(object);
212        }
213    }
214
215    return currentValue;
216}
217
218static bool canInjectNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
219{
220    if (!rootValue.isObject())
221        return false;
222
223    JSValue currentValue(rootValue);
224
225    ASSERT(index <= keyPathElements.size());
226    for (size_t i = 0; i < index; ++i) {
227        JSValue parentValue(currentValue);
228        const String& keyPathElement = keyPathElements[i];
229        if (!get(exec, parentValue, keyPathElement, currentValue))
230            return canSet(parentValue, keyPathElement);
231    }
232    return true;
233}
234
235bool injectIDBKeyIntoScriptValue(DOMRequestState* requestState, PassRefPtr<IDBKey> key, Deprecated::ScriptValue& value, const IDBKeyPath& keyPath)
236{
237    LOG(StorageAPI, "injectIDBKeyIntoScriptValue");
238
239    ASSERT(keyPath.type() == IDBKeyPath::StringType);
240
241    Vector<String> keyPathElements;
242    IDBKeyPathParseError error;
243    IDBParseKeyPath(keyPath.string(), keyPathElements, error);
244    ASSERT(error == IDBKeyPathParseErrorNone);
245
246    if (keyPathElements.isEmpty())
247        return false;
248
249    ExecState* exec = requestState->exec();
250
251    JSValue parent = ensureNthValueOnKeyPath(exec, value.jsValue(), keyPathElements, keyPathElements.size() - 1);
252    if (parent.isUndefined())
253        return false;
254
255    if (!set(exec, parent, keyPathElements.last(), idbKeyToJSValue(exec, jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), key.get())))
256        return false;
257
258    return true;
259}
260
261PassRefPtr<IDBKey> createIDBKeyFromScriptValueAndKeyPath(ExecState* exec, const Deprecated::ScriptValue& value, const IDBKeyPath& keyPath)
262{
263    LOG(StorageAPI, "createIDBKeyFromScriptValueAndKeyPath");
264    ASSERT(!keyPath.isNull());
265
266    if (keyPath.type() == IDBKeyPath::ArrayType) {
267        IDBKey::KeyArray result;
268        const Vector<String>& array = keyPath.array();
269        for (size_t i = 0; i < array.size(); i++) {
270            RefPtr<IDBKey> key = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, array[i]);
271            if (!key)
272                return 0;
273            result.append(key);
274        }
275        return IDBKey::createArray(result);
276    }
277
278    ASSERT(keyPath.type() == IDBKeyPath::StringType);
279    return internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, keyPath.string());
280}
281
282bool canInjectIDBKeyIntoScriptValue(DOMRequestState* requestState, const Deprecated::ScriptValue& scriptValue, const IDBKeyPath& keyPath)
283{
284    LOG(StorageAPI, "canInjectIDBKeyIntoScriptValue");
285
286    ASSERT(keyPath.type() == IDBKeyPath::StringType);
287    Vector<String> keyPathElements;
288    IDBKeyPathParseError error;
289    IDBParseKeyPath(keyPath.string(), keyPathElements, error);
290    ASSERT(error == IDBKeyPathParseErrorNone);
291
292    if (!keyPathElements.size())
293        return false;
294
295    JSC::ExecState* exec = requestState->exec();
296    return canInjectNthValueOnKeyPath(exec, scriptValue.jsValue(), keyPathElements, keyPathElements.size() - 1);
297}
298
299Deprecated::ScriptValue deserializeIDBValue(DOMRequestState* requestState, PassRefPtr<SerializedScriptValue> prpValue)
300{
301    ExecState* exec = requestState->exec();
302    RefPtr<SerializedScriptValue> serializedValue = prpValue;
303    JSValue result;
304    if (serializedValue)
305        result = serializedValue->deserialize(exec, exec->lexicalGlobalObject(), 0);
306    else
307        result = jsNull();
308    return Deprecated::ScriptValue(exec->vm(), result);
309}
310
311Deprecated::ScriptValue deserializeIDBValueBuffer(DOMRequestState* requestState, PassRefPtr<SharedBuffer> prpBuffer, bool keyIsDefined)
312{
313    if (prpBuffer) {
314        Vector<uint8_t> value;
315        value.append(prpBuffer->data(), prpBuffer->size());
316        return deserializeIDBValueBuffer(requestState->exec(), value, keyIsDefined);
317    }
318
319    return Deprecated::ScriptValue(requestState->exec()->vm(), jsNull());
320}
321
322Deprecated::ScriptValue deserializeIDBValueBuffer(JSC::ExecState* exec, const Vector<uint8_t>& buffer, bool keyIsDefined)
323{
324    // If the key doesn't exist, then the value must be undefined (as opposed to null).
325    if (!keyIsDefined) {
326        // We either shouldn't have a buffer or it should be of size 0.
327        ASSERT(!buffer.size());
328        return Deprecated::ScriptValue(exec->vm(), jsUndefined());
329    }
330
331    JSValue result;
332    if (buffer.size()) {
333        RefPtr<SerializedScriptValue> serializedValue = SerializedScriptValue::createFromWireBytes(buffer);
334        result = serializedValue->deserialize(exec, exec->lexicalGlobalObject(), 0, NonThrowing);
335    } else
336        result = jsNull();
337
338    return Deprecated::ScriptValue(exec->vm(), result);
339}
340
341Deprecated::ScriptValue idbKeyToScriptValue(DOMRequestState* requestState, PassRefPtr<IDBKey> key)
342{
343    ExecState* exec = requestState->exec();
344    return Deprecated::ScriptValue(exec->vm(), idbKeyToJSValue(exec, jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), key.get()));
345}
346
347PassRefPtr<IDBKey> scriptValueToIDBKey(DOMRequestState* requestState, const Deprecated::ScriptValue& scriptValue)
348{
349    ExecState* exec = requestState->exec();
350    return createIDBKeyFromValue(exec, scriptValue.jsValue());
351}
352
353void generateIndexKeysForValue(ExecState* exec, const IDBIndexMetadata& indexMetadata, const Deprecated::ScriptValue& objectValue, Vector<IDBKeyData>& indexKeys)
354{
355    RefPtr<IDBKey> indexKey = createIDBKeyFromScriptValueAndKeyPath(exec, objectValue, indexMetadata.keyPath);
356
357    if (!indexKey)
358        return;
359
360    if (!indexMetadata.multiEntry || indexKey->type() != IDBKey::ArrayType) {
361        if (!indexKey->isValid())
362            return;
363
364        indexKeys.append(IDBKeyData(indexKey.get()));
365    } else {
366        ASSERT(indexMetadata.multiEntry);
367        ASSERT(indexKey->type() == IDBKey::ArrayType);
368        indexKey = IDBKey::createMultiEntryArray(indexKey->array());
369
370        if (!indexKey->isValid())
371            return;
372
373        for (auto& i : indexKey->array())
374            indexKeys.append(IDBKeyData(i.get()));
375    }
376}
377
378} // namespace WebCore
379
380#endif // ENABLE(INDEXED_DATABASE)
381