blob: b9f46750a9fee78e36164a7f807a22c0ed993612 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/web_package/web_bundle_builder.h"
#include <string.h>
#include <ostream>
#include "base/big_endian.h"
namespace web_package {
namespace {
cbor::Value CreateByteString(base::StringPiece s) {
return cbor::Value(base::as_bytes(base::make_span(s)));
}
cbor::Value CreateHeaderMap(const WebBundleBuilder::Headers& headers) {
cbor::Value::MapValue map;
for (const auto& pair : headers)
map.insert({CreateByteString(pair.first), CreateByteString(pair.second)});
return cbor::Value(std::move(map));
}
// TODO(myrzakereyms): replace this method with cbor::writer::GetNumUintBytes.
uint64_t GetNumUintBytes(uint64_t value) {
if (value < 24) {
return 0;
} else if (value <= 0xFF) {
return 1;
} else if (value <= 0xFFFF) {
return 2;
} else if (value <= 0xFFFFFFFF) {
return 4;
}
return 8;
}
} // namespace
WebBundleBuilder::WebBundleBuilder(const std::string& fallback_url,
const std::string& manifest_url,
BundleVersion version,
bool allow_invalid_utf8_strings_for_testing)
: fallback_url_(fallback_url), version_(version) {
writer_config_.allow_invalid_utf8_for_testing =
allow_invalid_utf8_strings_for_testing;
if (!manifest_url.empty() && version == BundleVersion::kB1) {
AddSection("manifest", GetCborValueOfURL(manifest_url));
}
if (version == BundleVersion::kB2 && !fallback_url_.empty()) {
AddSection("primary", GetCborValueOfURL(fallback_url_));
}
}
WebBundleBuilder::~WebBundleBuilder() = default;
cbor::Value WebBundleBuilder::GetCborValueOfURL(base::StringPiece url) {
if (writer_config_.allow_invalid_utf8_for_testing) {
return cbor::Value::InvalidUTF8StringValueForTesting(url);
}
return cbor::Value(url);
}
void WebBundleBuilder::AddExchange(base::StringPiece url,
const Headers& response_headers,
base::StringPiece payload) {
AddIndexEntry(url, "", {AddResponse(response_headers, payload)});
}
WebBundleBuilder::ResponseLocation WebBundleBuilder::AddResponse(
const Headers& headers,
base::StringPiece payload) {
cbor::Value::ArrayValue response_array;
response_array.emplace_back(Encode(CreateHeaderMap(headers)));
response_array.emplace_back(CreateByteString(payload));
cbor::Value response(response_array);
int64_t response_length = EncodedLength(response);
ResponseLocation result = {current_responses_offset_, response_length};
current_responses_offset_ += response_length;
responses_.emplace_back(std::move(response));
return result;
}
void WebBundleBuilder::AddIndexEntry(
base::StringPiece url,
base::StringPiece variants_value,
std::vector<ResponseLocation> response_locations) {
// 'b2' version does not include |variants_value| in the response array.
if (version_ != BundleVersion::kB1) {
DCHECK_LE(response_locations.size(), 1u);
}
delayed_index_.insert(
{std::string(url), std::make_pair(std::string(variants_value),
std::move(response_locations))});
}
void WebBundleBuilder::AddSection(base::StringPiece name, cbor::Value section) {
section_lengths_.emplace_back(name);
section_lengths_.emplace_back(EncodedLength(section));
sections_.emplace_back(std::move(section));
}
void WebBundleBuilder::AddAuthority(cbor::Value::MapValue authority) {
authorities_.emplace_back(std::move(authority));
}
void WebBundleBuilder::AddVouchedSubset(cbor::Value::MapValue vouched_subset) {
vouched_subsets_.emplace_back(std::move(vouched_subset));
}
std::vector<uint8_t> WebBundleBuilder::CreateBundle() {
// Now that we know how many responses will be in the bundle,
// we want to shift all the offsets by the bytes required
// for the CBOR Array header and actually construct the index
// section.
int64_t initial_offset = 1 + GetNumUintBytes(responses_.size());
cbor::Value::MapValue index;
for (auto& entry : delayed_index_) {
auto& index_entry = entry.second;
cbor::Value::ArrayValue index_value_array;
if (version_ == BundleVersion::kB1) {
index_value_array.emplace_back(CreateByteString(index_entry.first));
}
for (auto& location : index_entry.second) {
index_value_array.emplace_back(location.offset + initial_offset);
index_value_array.emplace_back(location.length);
}
index.insert(
{GetCborValueOfURL(entry.first), cbor::Value(index_value_array)});
}
AddSection("index", cbor::Value(index));
if (!authorities_.empty() || !vouched_subsets_.empty()) {
cbor::Value::ArrayValue signatures_section;
signatures_section.emplace_back(std::move(authorities_));
signatures_section.emplace_back(std::move(vouched_subsets_));
AddSection("signatures", cbor::Value(std::move(signatures_section)));
}
AddSection("responses", cbor::Value(responses_));
return CreateTopLevel();
}
cbor::Value WebBundleBuilder::CreateEncodedSigned(
base::StringPiece validity_url,
base::StringPiece auth_sha256,
int64_t date,
int64_t expires,
base::StringPiece url,
base::StringPiece header_sha256,
base::StringPiece payload_integrity_header) {
cbor::Value::ArrayValue subset_hash_value;
subset_hash_value.emplace_back(CreateByteString("")); // variants-value
subset_hash_value.emplace_back(CreateByteString(header_sha256));
subset_hash_value.emplace_back(payload_integrity_header);
cbor::Value::MapValue subset_hashes;
subset_hashes.emplace(GetCborValueOfURL(url), std::move(subset_hash_value));
cbor::Value::MapValue signed_subset;
signed_subset.emplace("validity-url", validity_url);
signed_subset.emplace("auth-sha256", CreateByteString(auth_sha256));
signed_subset.emplace("date", date);
signed_subset.emplace("expires", expires);
signed_subset.emplace("subset-hashes", std::move(subset_hashes));
return cbor::Value(Encode(cbor::Value(signed_subset)));
}
std::vector<uint8_t> WebBundleBuilder::CreateTopLevel() {
cbor::Value::ArrayValue toplevel_array;
toplevel_array.emplace_back(
CreateByteString(u8"\U0001F310\U0001F4E6")); // "🌐📦"
if (version_ == BundleVersion::kB1) {
toplevel_array.emplace_back(
CreateByteString(base::StringPiece("b1\0\0", 4)));
toplevel_array.emplace_back(GetCborValueOfURL(fallback_url_));
} else {
toplevel_array.emplace_back(
CreateByteString(base::StringPiece("b2\0\0", 4)));
}
toplevel_array.emplace_back(Encode(cbor::Value(section_lengths_)));
toplevel_array.emplace_back(sections_);
// Put a dummy 8-byte bytestring.
toplevel_array.emplace_back(cbor::Value::BinaryValue(8, 0));
std::vector<uint8_t> bundle = Encode(cbor::Value(toplevel_array));
char encoded[8];
base::WriteBigEndian(encoded, static_cast<uint64_t>(bundle.size()));
// Overwrite the dummy bytestring with the actual size.
memcpy(bundle.data() + bundle.size() - 8, encoded, 8);
return bundle;
}
std::vector<uint8_t> WebBundleBuilder::Encode(const cbor::Value& value) {
return *cbor::Writer::Write(value, writer_config_);
}
int64_t WebBundleBuilder::EncodedLength(const cbor::Value& value) {
return Encode(value).size();
}
} // namespace web_package