// 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 #include #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(BundleVersion version, bool allow_invalid_utf8_strings_for_testing) : version_(version) { // Currently the only supported bundle format is b2. DCHECK_EQ(version_, BundleVersion::kB2); writer_config_.allow_invalid_utf8_for_testing = allow_invalid_utf8_strings_for_testing; } 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, const ResponseLocation& response_location) { delayed_index_.insert({std::string{url}, response_location}); } 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)); } void WebBundleBuilder::AddPrimaryURL(base::StringPiece url) { AddSection("primary", GetCborValueOfURL(url)); } std::vector 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_) { const ResponseLocation& location = entry.second; cbor::Value::ArrayValue index_value_array; 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 WebBundleBuilder::CreateTopLevel() { cbor::Value::ArrayValue toplevel_array; toplevel_array.emplace_back(CreateByteString("🌐📦")); 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 bundle = Encode(cbor::Value(toplevel_array)); char encoded[8]; base::WriteBigEndian(encoded, static_cast(bundle.size())); // Overwrite the dummy bytestring with the actual size. memcpy(bundle.data() + bundle.size() - 8, encoded, 8); return bundle; } std::vector 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