| /* |
| * 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) |