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