| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-shared.h" |
| #include "third_party/blink/public/platform/web_blob_info.h" |
| #include "third_party/blink/public/platform/web_data.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/serialization_tag.h" |
| #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/to_v8_for_modules.h" |
| #include "third_party/blink/renderer/modules/indexeddb/idb_any.h" |
| #include "third_party/blink/renderer/modules/indexeddb/idb_key.h" |
| #include "third_party/blink/renderer/modules/indexeddb/idb_key_path.h" |
| #include "third_party/blink/renderer/modules/indexeddb/idb_value.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" |
| #include "third_party/blink/renderer/platform/wtf/shared_buffer.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_view.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| v8::Local<v8::Object> EvaluateScriptAsObject(V8TestingScope& scope, |
| const char* source) { |
| v8::Local<v8::Script> script = |
| v8::Script::Compile(scope.GetContext(), |
| V8String(scope.GetIsolate(), source)) |
| .ToLocalChecked(); |
| return script->Run(scope.GetContext()).ToLocalChecked().As<v8::Object>(); |
| } |
| |
| std::unique_ptr<IDBKey> ScriptToKey(V8TestingScope& scope, const char* source) { |
| NonThrowableExceptionState exception_state; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| v8::Local<v8::Context> context = scope.GetContext(); |
| v8::Local<v8::Script> script = |
| v8::Script::Compile(context, V8String(isolate, source)).ToLocalChecked(); |
| v8::Local<v8::Value> value = script->Run(context).ToLocalChecked(); |
| return ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, ScriptValue(isolate, value), exception_state); |
| } |
| |
| std::unique_ptr<IDBKey> CheckKeyFromValueAndKeyPathInternal( |
| v8::Isolate* isolate, |
| const ScriptValue& value, |
| const String& key_path) { |
| IDBKeyPath idb_key_path(key_path); |
| EXPECT_TRUE(idb_key_path.IsValid()); |
| |
| NonThrowableExceptionState exception_state; |
| return ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, value, exception_state, idb_key_path); |
| } |
| |
| void CheckKeyPathNullValue(v8::Isolate* isolate, |
| const ScriptValue& value, |
| const String& key_path) { |
| ASSERT_FALSE(CheckKeyFromValueAndKeyPathInternal(isolate, value, key_path)); |
| } |
| |
| bool InjectKey(ScriptState* script_state, |
| IDBKey* key, |
| ScriptValue& value, |
| const String& key_path) { |
| IDBKeyPath idb_key_path(key_path); |
| EXPECT_TRUE(idb_key_path.IsValid()); |
| ScriptValue key_value = ScriptValue::From(script_state, key); |
| return InjectV8KeyIntoV8Value(script_state->GetIsolate(), key_value.V8Value(), |
| value.V8Value(), idb_key_path); |
| } |
| |
| void CheckInjection(ScriptState* script_state, |
| IDBKey* key, |
| ScriptValue& value, |
| const String& key_path) { |
| bool result = InjectKey(script_state, key, value, key_path); |
| ASSERT_TRUE(result); |
| std::unique_ptr<IDBKey> extracted_key = CheckKeyFromValueAndKeyPathInternal( |
| script_state->GetIsolate(), value, key_path); |
| EXPECT_TRUE(key->IsEqual(extracted_key.get())); |
| } |
| |
| void CheckInjectionIgnored(ScriptState* script_state, |
| IDBKey* key, |
| ScriptValue& value, |
| const String& key_path) { |
| bool result = InjectKey(script_state, key, value, key_path); |
| ASSERT_TRUE(result); |
| std::unique_ptr<IDBKey> extracted_key = CheckKeyFromValueAndKeyPathInternal( |
| script_state->GetIsolate(), value, key_path); |
| EXPECT_FALSE(key->IsEqual(extracted_key.get())); |
| } |
| |
| void CheckInjectionDisallowed(ScriptState* script_state, |
| ScriptValue& value, |
| const String& key_path) { |
| const IDBKeyPath idb_key_path(key_path); |
| ASSERT_TRUE(idb_key_path.IsValid()); |
| EXPECT_FALSE(CanInjectIDBKeyIntoScriptValue(script_state->GetIsolate(), value, |
| idb_key_path)); |
| } |
| |
| void CheckKeyPathStringValue(v8::Isolate* isolate, |
| const ScriptValue& value, |
| const String& key_path, |
| const String& expected) { |
| std::unique_ptr<IDBKey> idb_key = |
| CheckKeyFromValueAndKeyPathInternal(isolate, value, key_path); |
| ASSERT_TRUE(idb_key); |
| ASSERT_EQ(mojom::IDBKeyType::String, idb_key->GetType()); |
| ASSERT_TRUE(expected == idb_key->GetString()); |
| } |
| |
| void CheckKeyPathNumberValue(v8::Isolate* isolate, |
| const ScriptValue& value, |
| const String& key_path, |
| int expected) { |
| std::unique_ptr<IDBKey> idb_key = |
| CheckKeyFromValueAndKeyPathInternal(isolate, value, key_path); |
| ASSERT_TRUE(idb_key); |
| ASSERT_EQ(mojom::IDBKeyType::Number, idb_key->GetType()); |
| ASSERT_TRUE(expected == idb_key->Number()); |
| } |
| |
| // Compare a key against an array of keys. Supports keys with "holes" (keys of |
| // type None), so IDBKey::Compare() can't be used directly. |
| void CheckArrayKey(const IDBKey* key, const IDBKey::KeyArray& expected) { |
| EXPECT_EQ(mojom::IDBKeyType::Array, key->GetType()); |
| const IDBKey::KeyArray& array = key->Array(); |
| EXPECT_EQ(expected.size(), array.size()); |
| for (wtf_size_t i = 0; i < array.size(); ++i) { |
| EXPECT_EQ(array[i]->GetType(), expected[i]->GetType()); |
| if (array[i]->GetType() != mojom::IDBKeyType::None) { |
| EXPECT_EQ(0, expected[i]->Compare(array[i].get())); |
| } |
| } |
| } |
| |
| // SerializedScriptValue header format offsets are inferred from the Blink and |
| // V8 serialization code. The code below DCHECKs that |
| constexpr static size_t kSSVHeaderBlinkVersionTagOffset = 0; |
| constexpr static size_t kSSVHeaderBlinkVersionOffset = 1; |
| constexpr static size_t kSSVHeaderV8VersionTagOffset = 2; |
| // constexpr static size_t kSSVHeaderV8VersionOffset = 3; |
| |
| // Follows the same steps as the IndexedDB value serialization code. |
| void SerializeV8Value(v8::Local<v8::Value> value, |
| v8::Isolate* isolate, |
| Vector<char>* wire_bytes) { |
| NonThrowableExceptionState non_throwable_exception_state; |
| |
| SerializedScriptValue::SerializeOptions options; |
| scoped_refptr<SerializedScriptValue> serialized_value = |
| SerializedScriptValue::Serialize(isolate, value, options, |
| non_throwable_exception_state); |
| base::span<const uint8_t> ssv_wire_data = serialized_value->GetWireData(); |
| DCHECK(wire_bytes->IsEmpty()); |
| wire_bytes->Append(ssv_wire_data.data(), |
| static_cast<wtf_size_t>(ssv_wire_data.size())); |
| |
| // Sanity check that the serialization header has not changed, as the tests |
| // that use this method rely on the header format. |
| // |
| // The cast from char* to unsigned char* is necessary to avoid VS2015 warning |
| // C4309 (truncation of constant value). This happens because VersionTag is |
| // 0xFF. |
| const unsigned char* wire_data = |
| reinterpret_cast<unsigned char*>(wire_bytes->data()); |
| ASSERT_EQ(static_cast<unsigned char>(kVersionTag), |
| wire_data[kSSVHeaderBlinkVersionTagOffset]); |
| ASSERT_EQ( |
| static_cast<unsigned char>(SerializedScriptValue::kWireFormatVersion), |
| wire_data[kSSVHeaderBlinkVersionOffset]); |
| |
| ASSERT_EQ(static_cast<unsigned char>(kVersionTag), |
| wire_data[kSSVHeaderV8VersionTagOffset]); |
| // TODO(jbroman): Use the compile-time constant for V8 data format version. |
| // ASSERT_EQ(v8::ValueSerializer::GetCurrentDataFormatVersion(), |
| // wire_data[kSSVHeaderV8VersionOffset]); |
| } |
| |
| std::unique_ptr<IDBValue> CreateIDBValue(v8::Isolate* isolate, |
| Vector<char>& wire_bytes, |
| double primary_key, |
| const String& key_path) { |
| WebData web_data(SharedBuffer::AdoptVector(wire_bytes)); |
| scoped_refptr<SharedBuffer> data(web_data); |
| auto value = std::make_unique<IDBValue>(data, Vector<WebBlobInfo>()); |
| value->SetInjectedPrimaryKey(IDBKey::CreateNumber(primary_key), |
| IDBKeyPath(key_path)); |
| |
| value->SetIsolate(isolate); |
| return value; |
| } |
| |
| TEST(IDBKeyFromValueAndKeyPathTest, TopLevelPropertyStringValue) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| // object = { foo: "zoo" } |
| ScriptValue script_value = V8ObjectBuilder(scope.GetScriptState()) |
| .Add("foo", "zoo") |
| .GetScriptValue(); |
| CheckKeyPathStringValue(isolate, script_value, "foo", "zoo"); |
| CheckKeyPathNullValue(isolate, script_value, "bar"); |
| } |
| |
| } // namespace |
| |
| TEST(IDBKeyFromValueAndKeyPathTest, TopLevelPropertyNumberValue) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| // object = { foo: 456 } |
| ScriptValue script_value = V8ObjectBuilder(scope.GetScriptState()) |
| .AddNumber("foo", 456) |
| .GetScriptValue(); |
| CheckKeyPathNumberValue(isolate, script_value, "foo", 456); |
| CheckKeyPathNullValue(isolate, script_value, "bar"); |
| } |
| |
| TEST(IDBKeyFromValueAndKeyPathTest, SubProperty) { |
| V8TestingScope scope; |
| ScriptState* script_state = scope.GetScriptState(); |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| // object = { foo: { bar: "zee" } } |
| ScriptValue script_value = |
| V8ObjectBuilder(script_state) |
| .Add("foo", V8ObjectBuilder(script_state).Add("bar", "zee")) |
| .GetScriptValue(); |
| CheckKeyPathStringValue(isolate, script_value, "foo.bar", "zee"); |
| CheckKeyPathNullValue(isolate, script_value, "bar"); |
| } |
| |
| TEST(IDBKeyFromValue, Number) { |
| V8TestingScope scope; |
| |
| auto key = ScriptToKey(scope, "42.0"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Number); |
| EXPECT_EQ(key->Number(), 42); |
| |
| EXPECT_FALSE(ScriptToKey(scope, "NaN")->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, Date) { |
| V8TestingScope scope; |
| |
| auto key = ScriptToKey(scope, "new Date(123)"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Date); |
| EXPECT_EQ(key->Date(), 123); |
| |
| EXPECT_FALSE(ScriptToKey(scope, "new Date(NaN)")->IsValid()); |
| EXPECT_FALSE(ScriptToKey(scope, "new Date(Infinity)")->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, String) { |
| V8TestingScope scope; |
| |
| auto key = ScriptToKey(scope, "'abc'"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::String); |
| EXPECT_EQ(key->GetString(), "abc"); |
| } |
| |
| TEST(IDBKeyFromValue, Binary) { |
| V8TestingScope scope; |
| |
| // Key which is an ArrayBuffer. |
| { |
| auto key = ScriptToKey(scope, "new ArrayBuffer(3)"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Binary); |
| EXPECT_EQ(key->Binary()->size(), 3UL); |
| } |
| |
| // Key which is a TypedArray view on an ArrayBuffer. |
| { |
| auto key = ScriptToKey(scope, "new Uint8Array([0,1,2])"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Binary); |
| EXPECT_EQ(key->Binary()->size(), 3UL); |
| } |
| } |
| |
| TEST(IDBKeyFromValue, InvalidSimpleKeyTypes) { |
| V8TestingScope scope; |
| |
| const char* cases[] = { |
| "true", "false", "null", "undefined", "{}", "(function(){})", "/regex/", |
| }; |
| |
| for (const char* expr : cases) |
| EXPECT_FALSE(ScriptToKey(scope, expr)->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, SimpleArrays) { |
| V8TestingScope scope; |
| |
| { |
| auto key = ScriptToKey(scope, "[]"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Array); |
| EXPECT_EQ(key->Array().size(), 0UL); |
| } |
| |
| { |
| auto key = ScriptToKey(scope, "[0, 'abc']"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Array); |
| |
| const IDBKey::KeyArray& array = key->Array(); |
| EXPECT_EQ(array.size(), 2UL); |
| EXPECT_EQ(array[0]->GetType(), mojom::IDBKeyType::Number); |
| EXPECT_EQ(array[1]->GetType(), mojom::IDBKeyType::String); |
| } |
| } |
| |
| TEST(IDBKeyFromValue, NestedArray) { |
| V8TestingScope scope; |
| |
| auto key = ScriptToKey(scope, "[0, ['xyz', Infinity], 'abc']"); |
| EXPECT_EQ(key->GetType(), mojom::IDBKeyType::Array); |
| |
| const IDBKey::KeyArray& array = key->Array(); |
| EXPECT_EQ(array.size(), 3UL); |
| EXPECT_EQ(array[0]->GetType(), mojom::IDBKeyType::Number); |
| EXPECT_EQ(array[1]->GetType(), mojom::IDBKeyType::Array); |
| EXPECT_EQ(array[1]->Array().size(), 2UL); |
| EXPECT_EQ(array[1]->Array()[0]->GetType(), mojom::IDBKeyType::String); |
| EXPECT_EQ(array[1]->Array()[1]->GetType(), mojom::IDBKeyType::Number); |
| EXPECT_EQ(array[2]->GetType(), mojom::IDBKeyType::String); |
| } |
| |
| TEST(IDBKeyFromValue, CircularArray) { |
| V8TestingScope scope; |
| auto key = ScriptToKey(scope, |
| "(() => {" |
| " const a = [];" |
| " a.push(a);" |
| " return a;" |
| "})()"); |
| EXPECT_FALSE(key->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, DeepArray) { |
| V8TestingScope scope; |
| auto key = ScriptToKey(scope, |
| "(() => {" |
| " let a = [];" |
| " for (let i = 0; i < 10000; ++i) { a.push(a); }" |
| " return a;" |
| "})()"); |
| EXPECT_FALSE(key->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, SparseArray) { |
| V8TestingScope scope; |
| auto key = ScriptToKey(scope, "[,1]"); |
| EXPECT_FALSE(key->IsValid()); |
| |
| // Ridiculously large sparse array - ensure we check before allocating. |
| key = ScriptToKey(scope, "Object.assign([], {length: 2e9})"); |
| EXPECT_FALSE(key->IsValid()); |
| |
| // Large sparse arrays as subkeys - ensure we check while recursing. |
| key = ScriptToKey(scope, "[Object.assign([], {length: 2e9})]"); |
| EXPECT_FALSE(key->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, ShrinkingArray) { |
| V8TestingScope scope; |
| auto key = ScriptToKey( |
| scope, |
| "(() => {" |
| " const a = [0, 1, 2];" |
| " Object.defineProperty(a, 1, {get: () => { a.length = 2; return 1; }});" |
| " return a;" |
| "})()"); |
| EXPECT_FALSE(key->IsValid()); |
| } |
| |
| TEST(IDBKeyFromValue, Exceptions) { |
| V8TestingScope scope; |
| |
| const char* cases[] = { |
| // Detached ArrayBuffer. |
| "(() => {" |
| " const a = new ArrayBuffer(3);" |
| " postMessage(a, '*', [a]);" |
| " return a;" |
| "})()", |
| |
| // Detached ArrayBuffer view. |
| "(() => {" |
| " const a = new Uint8Array([0,1,2]);" |
| " postMessage(a.buffer, '*', [a.buffer]);" |
| " return a;" |
| "})()", |
| |
| // Value is an array with a getter that throws. |
| "(()=>{" |
| " const a = [0, 1, 2];" |
| " Object.defineProperty(a, 1, {get: () => { throw Error(); }});" |
| " return a;" |
| "})()", |
| |
| // Value is an array containing an array with a getter that throws. |
| "(()=>{" |
| " const a = [0, 1, 2];" |
| " Object.defineProperty(a, 1, {get: () => { throw Error(); }});" |
| " return ['x', a, 'z'];" |
| "})()", |
| |
| // Array with unconvertable item |
| "(() => {" |
| " const a = new ArrayBuffer(3);" |
| " postMessage(a, '*', [a]);" |
| " return [a];" |
| "})()", |
| }; |
| |
| for (const char* source : cases) { |
| ScriptValue script_value(scope.GetIsolate(), |
| EvaluateScriptAsObject(scope, source)); |
| |
| DummyExceptionStateForTesting exception_state; |
| auto key = ScriptValue::To<std::unique_ptr<IDBKey>>( |
| scope.GetIsolate(), script_value, exception_state); |
| EXPECT_FALSE(key->IsValid()); |
| EXPECT_TRUE(exception_state.HadException()); |
| } |
| } |
| |
| TEST(IDBKeyFromValueAndKeyPathTest, Exceptions) { |
| V8TestingScope scope; |
| ScriptValue script_value( |
| scope.GetIsolate(), |
| EvaluateScriptAsObject(scope, |
| "({id:1, get throws() { throw Error(); }})")); |
| { |
| // Key path references a property that throws. |
| DummyExceptionStateForTesting exception_state; |
| EXPECT_FALSE(ScriptValue::To<std::unique_ptr<IDBKey>>( |
| scope.GetIsolate(), script_value, exception_state, |
| IDBKeyPath("throws"))); |
| EXPECT_TRUE(exception_state.HadException()); |
| } |
| |
| { |
| // Compound key path references a property that throws. |
| DummyExceptionStateForTesting exception_state; |
| EXPECT_FALSE(ScriptValue::To<std::unique_ptr<IDBKey>>( |
| scope.GetIsolate(), script_value, exception_state, |
| IDBKeyPath(Vector<String>{"id", "throws"}))); |
| EXPECT_TRUE(exception_state.HadException()); |
| } |
| |
| { |
| // Compound key path references a property that throws, index case. |
| DummyExceptionStateForTesting exception_state; |
| EXPECT_FALSE(ScriptValue::To<std::unique_ptr<IDBKey>>( |
| scope.GetIsolate(), script_value, exception_state, |
| /*store_key_path=*/IDBKeyPath("id"), |
| /*index_key_path=*/IDBKeyPath(Vector<String>{"id", "throws"}))); |
| EXPECT_TRUE(exception_state.HadException()); |
| } |
| } |
| |
| TEST(IDBKeyFromValueAndKeyPathsTest, IndexKeys) { |
| V8TestingScope scope; |
| ScriptState* script_state = scope.GetScriptState(); |
| v8::Isolate* isolate = scope.GetIsolate(); |
| NonThrowableExceptionState exception_state; |
| |
| // object = { foo: { bar: "zee" }, bad: null } |
| ScriptValue script_value = |
| V8ObjectBuilder(script_state) |
| .Add("foo", V8ObjectBuilder(script_state).Add("bar", "zee")) |
| .AddNull("bad") |
| .GetScriptValue(); |
| |
| // Index key path member matches store key path. |
| std::unique_ptr<IDBKey> key = ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, script_value, exception_state, |
| /*store_key_path=*/IDBKeyPath("id"), |
| /*index_key_path=*/IDBKeyPath(Vector<String>{"id", "foo.bar"})); |
| IDBKey::KeyArray expected; |
| expected.emplace_back(IDBKey::CreateNone()); |
| expected.emplace_back(IDBKey::CreateString("zee")); |
| CheckArrayKey(key.get(), expected); |
| |
| // Index key path member matches, but there are unmatched members too. |
| EXPECT_FALSE(ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, script_value, exception_state, |
| /*store_key_path=*/IDBKeyPath("id"), |
| /*index_key_path=*/IDBKeyPath(Vector<String>{"id", "foo.bar", "nope"}))); |
| |
| // Index key path member matches, but there are invalid subkeys too. |
| EXPECT_FALSE( |
| ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, script_value, exception_state, |
| /*store_key_path=*/IDBKeyPath("id"), |
| /*index_key_path=*/IDBKeyPath(Vector<String>{"id", "foo.bar", "bad"})) |
| ->IsValid()); |
| |
| // Index key path member does not match store key path. |
| EXPECT_FALSE(ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, script_value, exception_state, |
| /*store_key_path=*/IDBKeyPath("id"), |
| /*index_key_path=*/IDBKeyPath(Vector<String>{"id2", "foo.bar"}))); |
| |
| // Index key path is not array, matches store key path. |
| EXPECT_FALSE(ScriptValue::To<std::unique_ptr<IDBKey>>( |
| isolate, script_value, exception_state, |
| /*store_key_path=*/IDBKeyPath("id"), |
| /*index_key_path=*/IDBKeyPath("id"))); |
| } |
| |
| TEST(InjectIDBKeyTest, ImplicitValues) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| { |
| v8::Local<v8::String> string = V8String(isolate, "string"); |
| ScriptValue value = ScriptValue(scope.GetIsolate(), string); |
| std::unique_ptr<IDBKey> idb_key = IDBKey::CreateNumber(123); |
| CheckInjectionIgnored(scope.GetScriptState(), idb_key.get(), value, |
| "length"); |
| } |
| { |
| v8::Local<v8::Array> array = v8::Array::New(isolate); |
| ScriptValue value = ScriptValue(scope.GetIsolate(), array); |
| std::unique_ptr<IDBKey> idb_key = IDBKey::CreateNumber(456); |
| CheckInjectionIgnored(scope.GetScriptState(), idb_key.get(), value, |
| "length"); |
| } |
| } |
| |
| TEST(InjectIDBKeyTest, TopLevelPropertyStringValue) { |
| V8TestingScope scope; |
| |
| // object = { foo: "zoo" } |
| ScriptValue script_object = V8ObjectBuilder(scope.GetScriptState()) |
| .Add("foo", "zoo") |
| .GetScriptValue(); |
| std::unique_ptr<IDBKey> idb_string_key = IDBKey::CreateString("myNewKey"); |
| CheckInjection(scope.GetScriptState(), idb_string_key.get(), script_object, |
| "bar"); |
| std::unique_ptr<IDBKey> idb_number_key = IDBKey::CreateNumber(1234); |
| CheckInjection(scope.GetScriptState(), idb_number_key.get(), script_object, |
| "bar"); |
| |
| CheckInjectionDisallowed(scope.GetScriptState(), script_object, "foo.bar"); |
| } |
| |
| TEST(InjectIDBKeyTest, SubProperty) { |
| V8TestingScope scope; |
| ScriptState* script_state = scope.GetScriptState(); |
| |
| // object = { foo: { bar: "zee" } } |
| ScriptValue script_object = |
| V8ObjectBuilder(script_state) |
| .Add("foo", V8ObjectBuilder(script_state).Add("bar", "zee")) |
| .GetScriptValue(); |
| |
| std::unique_ptr<IDBKey> idb_string_key = IDBKey::CreateString("myNewKey"); |
| CheckInjection(scope.GetScriptState(), idb_string_key.get(), script_object, |
| "foo.baz"); |
| std::unique_ptr<IDBKey> idb_number_key = IDBKey::CreateNumber(789); |
| CheckInjection(scope.GetScriptState(), idb_number_key.get(), script_object, |
| "foo.baz"); |
| std::unique_ptr<IDBKey> idb_date_key = IDBKey::CreateDate(4567); |
| CheckInjection(scope.GetScriptState(), idb_date_key.get(), script_object, |
| "foo.baz"); |
| CheckInjection(scope.GetScriptState(), idb_date_key.get(), script_object, |
| "bar"); |
| std::unique_ptr<IDBKey> idb_array_key = |
| IDBKey::CreateArray(IDBKey::KeyArray()); |
| CheckInjection(scope.GetScriptState(), idb_array_key.get(), script_object, |
| "foo.baz"); |
| CheckInjection(scope.GetScriptState(), idb_array_key.get(), script_object, |
| "bar"); |
| |
| CheckInjectionDisallowed(scope.GetScriptState(), script_object, |
| "foo.bar.baz"); |
| std::unique_ptr<IDBKey> idb_zoo_key = IDBKey::CreateString("zoo"); |
| CheckInjection(scope.GetScriptState(), idb_zoo_key.get(), script_object, |
| "foo.xyz.foo"); |
| } |
| |
| TEST(DeserializeIDBValueTest, CurrentVersions) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| Vector<char> object_bytes; |
| v8::Local<v8::Object> empty_object = v8::Object::New(isolate); |
| SerializeV8Value(empty_object, isolate, &object_bytes); |
| std::unique_ptr<IDBValue> idb_value = |
| CreateIDBValue(isolate, object_bytes, 42.0, "foo"); |
| |
| v8::Local<v8::Value> v8_value = DeserializeIDBValue( |
| isolate, scope.GetContext()->Global(), idb_value.get()); |
| EXPECT_TRUE(!scope.GetExceptionState().HadException()); |
| |
| ASSERT_TRUE(v8_value->IsObject()); |
| v8::Local<v8::Object> v8_value_object = v8_value.As<v8::Object>(); |
| v8::Local<v8::Value> v8_number_value = |
| v8_value_object->Get(scope.GetContext(), V8AtomicString(isolate, "foo")) |
| .ToLocalChecked(); |
| ASSERT_TRUE(v8_number_value->IsNumber()); |
| v8::Local<v8::Number> v8_number = v8_number_value.As<v8::Number>(); |
| EXPECT_EQ(v8_number->Value(), 42.0); |
| } |
| |
| TEST(DeserializeIDBValueTest, FutureV8Version) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| // Pretend that the object was serialized by a future version of V8. |
| Vector<char> object_bytes; |
| v8::Local<v8::Object> empty_object = v8::Object::New(isolate); |
| SerializeV8Value(empty_object, isolate, &object_bytes); |
| object_bytes[kSSVHeaderV8VersionTagOffset] += 1; |
| |
| // The call sequence below mimics IndexedDB's usage pattern when attempting to |
| // read a value in an object store with a key generator and a key path, but |
| // the serialized value uses a newer format version. |
| // |
| // http://crbug.com/703704 has a reproduction for this test's circumstances. |
| std::unique_ptr<IDBValue> idb_value = |
| CreateIDBValue(isolate, object_bytes, 42.0, "foo"); |
| |
| v8::Local<v8::Value> v8_value = DeserializeIDBValue( |
| isolate, scope.GetContext()->Global(), idb_value.get()); |
| EXPECT_TRUE(!scope.GetExceptionState().HadException()); |
| EXPECT_TRUE(v8_value->IsNull()); |
| } |
| |
| TEST(DeserializeIDBValueTest, InjectionIntoNonObject) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| // Simulate a storage corruption where an object is read back as a number. |
| // This test uses a one-segment key path. |
| Vector<char> object_bytes; |
| v8::Local<v8::Number> number = v8::Number::New(isolate, 42.0); |
| SerializeV8Value(number, isolate, &object_bytes); |
| std::unique_ptr<IDBValue> idb_value = |
| CreateIDBValue(isolate, object_bytes, 42.0, "foo"); |
| |
| v8::Local<v8::Value> v8_value = DeserializeIDBValue( |
| isolate, scope.GetContext()->Global(), idb_value.get()); |
| EXPECT_TRUE(!scope.GetExceptionState().HadException()); |
| ASSERT_TRUE(v8_value->IsNumber()); |
| v8::Local<v8::Number> v8_number = v8_value.As<v8::Number>(); |
| EXPECT_EQ(v8_number->Value(), 42.0); |
| } |
| |
| TEST(DeserializeIDBValueTest, NestedInjectionIntoNonObject) { |
| V8TestingScope scope; |
| v8::Isolate* isolate = scope.GetIsolate(); |
| |
| // Simulate a storage corruption where an object is read back as a number. |
| // This test uses a multiple-segment key path. |
| Vector<char> object_bytes; |
| v8::Local<v8::Number> number = v8::Number::New(isolate, 42.0); |
| SerializeV8Value(number, isolate, &object_bytes); |
| std::unique_ptr<IDBValue> idb_value = |
| CreateIDBValue(isolate, object_bytes, 42.0, "foo.bar"); |
| |
| v8::Local<v8::Value> v8_value = DeserializeIDBValue( |
| isolate, scope.GetContext()->Global(), idb_value.get()); |
| EXPECT_TRUE(!scope.GetExceptionState().HadException()); |
| ASSERT_TRUE(v8_value->IsNumber()); |
| v8::Local<v8::Number> v8_number = v8_value.As<v8::Number>(); |
| EXPECT_EQ(v8_number->Value(), 42.0); |
| } |
| |
| } // namespace blink |