blob: b76a6c624971649e946d3ef061fab7ac0b26ccfa [file] [log] [blame]
/*
* 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