blob: c661cdaf02489aed164a7937764341cdac90139d [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/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/modules/indexeddb/web_idb_key_path.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/blink/renderer/platform/wtf/text/string_view.h"
namespace blink {
namespace {
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());
}
// 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 WebString& key_path) {
WebData web_data(SharedBuffer::AdoptVector(wire_bytes));
scoped_refptr<SharedBuffer> data(web_data);
std::unique_ptr<IDBValue> value =
IDBValue::Create(data, Vector<WebBlobInfo>());
value->SetInjectedPrimaryKey(IDBKey::CreateNumber(primary_key),
WebIDBKeyPath(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(InjectIDBKeyTest, ImplicitValues) {
V8TestingScope scope;
v8::Isolate* isolate = scope.GetIsolate();
{
v8::Local<v8::String> string = V8String(isolate, "string");
ScriptValue value = ScriptValue(scope.GetScriptState(), 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.GetScriptState(), 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