blob: 2d2c490b0bce218fcf5beeb026d3ac12c2560b5a [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "third_party/blink/public/common/messaging/string_message_codec.h"
#include <memory>
#include <string>
#include <vector>
#include "base/check_op.h"
#include "base/containers/buffer_iterator.h"
#include "base/containers/span.h"
#include "base/functional/overloaded.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/checked_math.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "third_party/blink/public/mojom/array_buffer/array_buffer_contents.mojom.h"
namespace blink {
namespace {
// An ArrayBufferPayload impl based on std::vector.
class VectorArrayBuffer : public WebMessageArrayBufferPayload {
public:
VectorArrayBuffer(std::vector<uint8_t> data, size_t position, size_t length)
: data_(std::move(data)), position_(position), length_(length) {
size_t size = base::CheckAdd(position_, length_).ValueOrDie();
CHECK_GE(data_.size(), size);
}
size_t GetLength() const override { return length_; }
bool GetIsResizableByUserJavaScript() const override {
// VectorArrayBuffers are not used for ArrayBuffer transfers and are
// currently always fixed-length. Structured cloning resizables ArrayBuffers
// is not yet supported in SMC.
return false;
}
size_t GetMaxByteLength() const override { return length_; }
std::optional<base::span<const uint8_t>> GetAsSpanIfPossible()
const override {
return base::make_span(data_).subspan(position_, length_);
}
void CopyInto(base::span<uint8_t> dest) const override {
CHECK_GE(dest.size(), length_);
memcpy(dest.data(), data_.data() + position_, length_);
}
private:
std::vector<uint8_t> data_;
size_t position_;
size_t length_;
};
// An ArrayBufferPayload impl based on mojo::BigBuffer.
class BigBufferArrayBuffer : public WebMessageArrayBufferPayload {
public:
explicit BigBufferArrayBuffer(mojo_base::BigBuffer data,
std::optional<size_t> max_byte_length)
: data_(std::move(data)), max_byte_length_(max_byte_length) {
DCHECK(!max_byte_length || *max_byte_length >= GetLength());
}
size_t GetLength() const override { return data_.size(); }
bool GetIsResizableByUserJavaScript() const override {
return max_byte_length_.has_value();
}
size_t GetMaxByteLength() const override {
return max_byte_length_.value_or(GetLength());
}
std::optional<base::span<const uint8_t>> GetAsSpanIfPossible()
const override {
return base::make_span(data_);
}
void CopyInto(base::span<uint8_t> dest) const override {
CHECK(dest.size() >= data_.size());
memcpy(dest.data(), data_.data(), data_.size());
}
private:
mojo_base::BigBuffer data_;
std::optional<size_t> max_byte_length_;
};
const uint32_t kVarIntShift = 7;
const uint32_t kVarIntMask = (1 << kVarIntShift) - 1;
const uint8_t kVersionTag = 0xFF;
const uint8_t kPaddingTag = '\0';
// serialization_tag, see v8/src/objects/value-serializer.cc
const uint8_t kOneByteStringTag = '"';
const uint8_t kTwoByteStringTag = 'c';
const uint8_t kArrayBuffer = 'B';
const uint8_t kArrayBufferTransferTag = 't';
const uint32_t kVersion = 10;
static size_t BytesNeededForUint32(uint32_t value) {
size_t result = 0;
do {
result++;
value >>= kVarIntShift;
} while (value);
return result;
}
void WriteUint8(uint8_t value, std::vector<uint8_t>* buffer) {
buffer->push_back(value);
}
void WriteUint32(uint32_t value, std::vector<uint8_t>* buffer) {
for (;;) {
uint8_t b = (value & kVarIntMask);
value >>= kVarIntShift;
if (!value) {
WriteUint8(b, buffer);
break;
}
WriteUint8(b | (1 << kVarIntShift), buffer);
}
}
void WriteBytes(const char* bytes,
size_t num_bytes,
std::vector<uint8_t>* buffer) {
buffer->insert(buffer->end(), bytes, bytes + num_bytes);
}
bool ReadUint8(base::BufferIterator<const uint8_t>& iter, uint8_t* value) {
if (const uint8_t* ptr = iter.Object<uint8_t>()) {
*value = *ptr;
return true;
}
return false;
}
bool ReadUint32(base::BufferIterator<const uint8_t>& iter, uint32_t* value) {
*value = 0;
uint8_t current_byte;
int shift = 0;
do {
if (!ReadUint8(iter, &current_byte))
return false;
*value |= (static_cast<uint32_t>(current_byte & kVarIntMask) << shift);
shift += kVarIntShift;
} while (current_byte & (1 << kVarIntShift));
return true;
}
bool ContainsOnlyLatin1(const std::u16string& data) {
char16_t x = 0;
for (char16_t c : data)
x |= c;
return !(x & 0xFF00);
}
} // namespace
// static
std::unique_ptr<WebMessageArrayBufferPayload>
WebMessageArrayBufferPayload::CreateFromBigBuffer(
mojo_base::BigBuffer buffer,
std::optional<size_t> max_byte_length) {
return std::make_unique<BigBufferArrayBuffer>(std::move(buffer),
max_byte_length);
}
// static
std::unique_ptr<WebMessageArrayBufferPayload>
WebMessageArrayBufferPayload::CreateForTesting(std::vector<uint8_t> data) {
auto size = data.size();
return std::make_unique<VectorArrayBuffer>(std::move(data), 0, size);
}
TransferableMessage EncodeWebMessagePayload(const WebMessagePayload& payload) {
TransferableMessage message;
std::vector<uint8_t> buffer;
WriteUint8(kVersionTag, &buffer);
WriteUint32(kVersion, &buffer);
absl::visit(
base::Overloaded{
[&](const std::u16string& str) {
if (ContainsOnlyLatin1(str)) {
std::string data_latin1(str.cbegin(), str.cend());
WriteUint8(kOneByteStringTag, &buffer);
WriteUint32(data_latin1.size(), &buffer);
WriteBytes(data_latin1.c_str(), data_latin1.size(), &buffer);
} else {
size_t num_bytes = str.size() * sizeof(char16_t);
if ((buffer.size() + 1 + BytesNeededForUint32(num_bytes)) & 1)
WriteUint8(kPaddingTag, &buffer);
WriteUint8(kTwoByteStringTag, &buffer);
WriteUint32(num_bytes, &buffer);
WriteBytes(reinterpret_cast<const char*>(str.data()), num_bytes,
&buffer);
}
},
[&](const std::unique_ptr<WebMessageArrayBufferPayload>&
array_buffer) {
WriteUint8(kArrayBufferTransferTag, &buffer);
// Write at the first slot.
WriteUint32(0, &buffer);
mojo_base::BigBuffer big_buffer(array_buffer->GetLength());
array_buffer->CopyInto(base::make_span(big_buffer));
message.array_buffer_contents_array.push_back(
mojom::SerializedArrayBufferContents::New(
std::move(big_buffer),
array_buffer->GetIsResizableByUserJavaScript(),
array_buffer->GetMaxByteLength()));
}},
payload);
message.owned_encoded_message = std::move(buffer);
message.encoded_message = message.owned_encoded_message;
return message;
}
std::optional<WebMessagePayload> DecodeToWebMessagePayload(
TransferableMessage message) {
base::BufferIterator<const uint8_t> iter(message.encoded_message);
uint8_t tag;
// Discard the outer envelope, including trailer info if applicable.
if (!ReadUint8(iter, &tag))
return std::nullopt;
if (tag == kVersionTag) {
uint32_t version = 0;
if (!ReadUint32(iter, &version))
return std::nullopt;
static constexpr uint32_t kMinWireFormatVersionWithTrailer = 21;
if (version >= kMinWireFormatVersionWithTrailer) {
// In these versions, we expect kTrailerOffsetTag (0xFE) followed by an
// offset and size. See details in
// third_party/blink/renderer/core/v8/serialization/serialization_tag.h.
auto span = iter.Span<uint8_t>(1 + sizeof(uint64_t) + sizeof(uint32_t));
if (span.empty() || span[0] != 0xFE)
return std::nullopt;
}
if (!ReadUint8(iter, &tag))
return std::nullopt;
}
// Discard any leading version and padding tags.
while (tag == kVersionTag || tag == kPaddingTag) {
uint32_t version;
if (tag == kVersionTag && !ReadUint32(iter, &version))
return std::nullopt;
if (!ReadUint8(iter, &tag))
return std::nullopt;
}
switch (tag) {
case kOneByteStringTag: {
// Use of unsigned char rather than char here matters, so that Latin-1
// characters are zero-extended rather than sign-extended
uint32_t num_bytes;
if (!ReadUint32(iter, &num_bytes))
return std::nullopt;
auto span = iter.Span<unsigned char>(num_bytes / sizeof(unsigned char));
std::u16string str(span.begin(), span.end());
return span.size_bytes() == num_bytes
? std::make_optional(WebMessagePayload(std::move(str)))
: std::nullopt;
}
case kTwoByteStringTag: {
uint32_t num_bytes;
if (!ReadUint32(iter, &num_bytes))
return std::nullopt;
auto span = iter.Span<char16_t>(num_bytes / sizeof(char16_t));
std::u16string str(span.begin(), span.end());
return span.size_bytes() == num_bytes
? std::make_optional(WebMessagePayload(std::move(str)))
: std::nullopt;
}
case kArrayBuffer: {
uint32_t num_bytes;
if (!ReadUint32(iter, &num_bytes))
return std::nullopt;
size_t position = iter.position();
return position + num_bytes == iter.total_size()
? std::make_optional(
WebMessagePayload(std::make_unique<VectorArrayBuffer>(
std::move(message.owned_encoded_message), position,
num_bytes)))
: std::nullopt;
}
case kArrayBufferTransferTag: {
uint32_t array_buffer_index;
if (!ReadUint32(iter, &array_buffer_index))
return std::nullopt;
// We only support transfer ArrayBuffer at the first index.
if (array_buffer_index != 0)
return std::nullopt;
if (message.array_buffer_contents_array.size() != 1)
return std::nullopt;
auto& array_buffer_contents = message.array_buffer_contents_array[0];
std::optional<size_t> max_byte_length;
if (array_buffer_contents->is_resizable_by_user_javascript) {
max_byte_length.emplace(array_buffer_contents->max_byte_length);
}
return std::make_optional(
WebMessagePayload(std::make_unique<BigBufferArrayBuffer>(
std::move(array_buffer_contents->contents), max_byte_length)));
}
}
DLOG(WARNING) << "Unexpected tag: " << tag;
return std::nullopt;
}
} // namespace blink