blob: cd995e69c5980e2ba21cafbf3663ceae320ea485 [file] [log] [blame]
/*
* Copyright (C) 2014, 2016 Apple 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 INC. 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 INC. 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 "config.h"
#include "IDBSerialization.h"
#if ENABLE(INDEXED_DATABASE)
#include "IDBKeyData.h"
#include "IDBKeyPath.h"
#include "KeyedCoding.h"
#if USE(GLIB)
#include <glib.h>
#include <wtf/glib/GRefPtr.h>
#endif
namespace WebCore {
enum class KeyPathType { Null, String, Array };
RefPtr<SharedBuffer> serializeIDBKeyPath(const Optional<IDBKeyPath>& keyPath)
{
auto encoder = KeyedEncoder::encoder();
if (keyPath) {
auto visitor = WTF::makeVisitor([&](const String& string) {
encoder->encodeEnum("type", KeyPathType::String);
encoder->encodeString("string", string);
}, [&](const Vector<String>& vector) {
encoder->encodeEnum("type", KeyPathType::Array);
encoder->encodeObjects("array", vector.begin(), vector.end(), [](WebCore::KeyedEncoder& encoder, const String& string) {
encoder.encodeString("string", string);
});
});
WTF::visit(visitor, keyPath.value());
} else
encoder->encodeEnum("type", KeyPathType::Null);
return encoder->finishEncoding();
}
bool deserializeIDBKeyPath(const uint8_t* data, size_t size, Optional<IDBKeyPath>& result)
{
if (!data || !size)
return false;
auto decoder = KeyedDecoder::decoder(data, size);
KeyPathType type;
bool succeeded = decoder->decodeEnum("type", type, [](KeyPathType value) {
return value == KeyPathType::Null || value == KeyPathType::String || value == KeyPathType::Array;
});
if (!succeeded)
return false;
switch (type) {
case KeyPathType::Null:
break;
case KeyPathType::String: {
String string;
if (!decoder->decodeString("string", string))
return false;
result = IDBKeyPath(WTFMove(string));
break;
}
case KeyPathType::Array: {
Vector<String> vector;
succeeded = decoder->decodeObjects("array", vector, [](KeyedDecoder& decoder, String& result) {
return decoder.decodeString("string", result);
});
if (!succeeded)
return false;
result = IDBKeyPath(WTFMove(vector));
break;
}
}
return true;
}
static bool isLegacySerializedIDBKeyData(const uint8_t* data, size_t size)
{
#if USE(CF)
UNUSED_PARAM(size);
// This is the magic character that begins serialized PropertyLists, and tells us whether
// the key we're looking at is an old-style key.
static const uint8_t legacySerializedKeyVersion = 'b';
if (data[0] == legacySerializedKeyVersion)
return true;
#elif USE(GLIB)
// KeyedEncoderGLib uses a GVariant dictionary, so check if the given data is a valid GVariant dictionary.
GRefPtr<GBytes> bytes = adoptGRef(g_bytes_new(data, size));
GRefPtr<GVariant> variant = g_variant_new_from_bytes(G_VARIANT_TYPE("a{sv}"), bytes.get(), FALSE);
return g_variant_is_normal_form(variant.get());
#else
UNUSED_PARAM(data);
UNUSED_PARAM(size);
#endif
return false;
}
/*
The IDBKeyData serialization format is as follows:
[1 byte version header][Key Buffer]
The Key Buffer serialization format is as follows:
[1 byte key type][Type specific data]
Type specific serialization formats are as follows for each of the types:
Min:
[0 bytes]
Number:
[8 bytes representing a double encoded in little endian]
Date:
[8 bytes representing a double encoded in little endian]
String:
[4 bytes representing string "length" in little endian]["length" number of 2-byte pairs representing ECMAScript 16-bit code units]
Binary:
[8 bytes representing the "size" of the binary blob]["size" bytes]
Array:
[8 bytes representing the "length" of the key array]["length" individual Key Buffer entries]
Max:
[0 bytes]
*/
static const uint8_t SIDBKeyVersion = 0x00;
enum class SIDBKeyType : uint8_t {
Min = 0x00,
Number = 0x20,
Date = 0x40,
String = 0x60,
Binary = 0x80,
Array = 0xA0,
Max = 0xFF,
};
static SIDBKeyType serializedTypeForKeyType(IndexedDB::KeyType type)
{
switch (type) {
case IndexedDB::KeyType::Min:
return SIDBKeyType::Min;
case IndexedDB::KeyType::Number:
return SIDBKeyType::Number;
case IndexedDB::KeyType::Date:
return SIDBKeyType::Date;
case IndexedDB::KeyType::String:
return SIDBKeyType::String;
case IndexedDB::KeyType::Binary:
return SIDBKeyType::Binary;
case IndexedDB::KeyType::Array:
return SIDBKeyType::Array;
case IndexedDB::KeyType::Max:
return SIDBKeyType::Max;
case IndexedDB::KeyType::Invalid:
RELEASE_ASSERT_NOT_REACHED();
};
RELEASE_ASSERT_NOT_REACHED();
}
#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) || CPU(NEEDS_ALIGNED_ACCESS)
template <typename T> static void writeLittleEndian(Vector<char>& buffer, T value)
{
for (unsigned i = 0; i < sizeof(T); i++) {
buffer.append(value & 0xFF);
value >>= 8;
}
}
template <typename T> static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value)
{
if (ptr > end - sizeof(value))
return false;
value = 0;
for (size_t i = 0; i < sizeof(T); i++)
value += ((T)*ptr++) << (i * 8);
return true;
}
#else
template <typename T> static void writeLittleEndian(Vector<char>& buffer, T value)
{
buffer.append(reinterpret_cast<uint8_t*>(&value), sizeof(value));
}
template <typename T> static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value)
{
if (ptr > end - sizeof(value))
return false;
value = *reinterpret_cast<const T*>(ptr);
ptr += sizeof(T);
return true;
}
#endif
static void writeDouble(Vector<char>& data, double d)
{
writeLittleEndian(data, *reinterpret_cast<uint64_t*>(&d));
}
static bool readDouble(const uint8_t*& data, const uint8_t* end, double& d)
{
return readLittleEndian(data, end, *reinterpret_cast<uint64_t*>(&d));
}
static void encodeKey(Vector<char>& data, const IDBKeyData& key)
{
SIDBKeyType type = serializedTypeForKeyType(key.type());
data.append(static_cast<char>(type));
switch (type) {
case SIDBKeyType::Number:
writeDouble(data, key.number());
break;
case SIDBKeyType::Date:
writeDouble(data, key.date());
break;
case SIDBKeyType::String: {
auto string = key.string();
uint32_t length = string.length();
writeLittleEndian(data, length);
for (size_t i = 0; i < length; ++i)
writeLittleEndian(data, string[i]);
break;
}
case SIDBKeyType::Binary: {
auto& buffer = key.binary();
uint64_t size = buffer.size();
writeLittleEndian(data, size);
auto* bufferData = buffer.data();
ASSERT(bufferData || !size);
if (bufferData)
data.append(bufferData->data(), bufferData->size());
break;
}
case SIDBKeyType::Array: {
auto& array = key.array();
uint64_t size = array.size();
writeLittleEndian(data, size);
for (auto& key : array)
encodeKey(data, key);
break;
}
case SIDBKeyType::Min:
case SIDBKeyType::Max:
break;
}
}
RefPtr<SharedBuffer> serializeIDBKeyData(const IDBKeyData& key)
{
Vector<char> data;
data.append(SIDBKeyVersion);
encodeKey(data, key);
return SharedBuffer::create(WTFMove(data));
}
static bool decodeKey(const uint8_t*& data, const uint8_t* end, IDBKeyData& result)
{
if (!data || data >= end)
return false;
SIDBKeyType type = static_cast<SIDBKeyType>(data++[0]);
switch (type) {
case SIDBKeyType::Min:
result = IDBKeyData::minimum();
return true;
case SIDBKeyType::Max:
result = IDBKeyData::maximum();
return true;
case SIDBKeyType::Number: {
double d;
if (!readDouble(data, end, d))
return false;
result.setNumberValue(d);
return true;
}
case SIDBKeyType::Date: {
double d;
if (!readDouble(data, end, d))
return false;
result.setDateValue(d);
return true;
}
case SIDBKeyType::String: {
uint32_t length;
if (!readLittleEndian(data, end, length))
return false;
if (static_cast<uint64_t>(end - data) < length * 2)
return false;
Vector<UChar> buffer;
buffer.reserveInitialCapacity(length);
for (size_t i = 0; i < length; i++) {
uint16_t ch;
if (!readLittleEndian(data, end, ch))
return false;
buffer.uncheckedAppend(ch);
}
result.setStringValue(String::adopt(WTFMove(buffer)));
return true;
}
case SIDBKeyType::Binary: {
uint64_t size64;
if (!readLittleEndian(data, end, size64))
return false;
if (static_cast<uint64_t>(end - data) < size64)
return false;
if (size64 > std::numeric_limits<size_t>::max())
return false;
size_t size = static_cast<size_t>(size64);
Vector<uint8_t> dataVector;
dataVector.append(data, size);
data += size;
result.setBinaryValue(ThreadSafeDataBuffer::create(WTFMove(dataVector)));
return true;
}
case SIDBKeyType::Array: {
uint64_t size64;
if (!readLittleEndian(data, end, size64))
return false;
if (size64 > std::numeric_limits<size_t>::max())
return false;
size_t size = static_cast<size_t>(size64);
Vector<IDBKeyData> array;
array.reserveInitialCapacity(size);
for (size_t i = 0; i < size; ++i) {
IDBKeyData keyData;
if (!decodeKey(data, end, keyData))
return false;
ASSERT(keyData.isValid());
array.uncheckedAppend(WTFMove(keyData));
}
result.setArrayValue(array);
return true;
}
default:
LOG_ERROR("decodeKey encountered unexpected type: %i", (int)type);
return false;
}
}
bool deserializeIDBKeyData(const uint8_t* data, size_t size, IDBKeyData& result)
{
if (!data || !size)
return false;
if (isLegacySerializedIDBKeyData(data, size)) {
auto decoder = KeyedDecoder::decoder(data, size);
return IDBKeyData::decode(*decoder, result);
}
// Verify this is a SerializedIDBKey version we understand.
const uint8_t* current = data;
const uint8_t* end = data + size;
if (current++[0] != SIDBKeyVersion)
return false;
if (decodeKey(current, end, result)) {
// Even if we successfully decoded a key, the deserialize is only successful
// if we actually consumed all input data.
return current == end;
}
return false;
}
} // namespace WebCore
#endif // ENABLE(INDEXED_DATABASE)