blob: c298b0b62611543f61077e1a82ea9f44db457516 [file] [log] [blame]
// Copyright 2018 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 "content/browser/web_package/signed_exchange_prologue.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/web_package/signed_exchange_utils.h"
namespace content {
namespace {
constexpr char kSignedExchangeMagic[] = "sxg1-b2";
// size of `fallbackUrlLength` field in number of bytes.
constexpr size_t kFallbackUrlLengthFieldSizeInBytes = 2;
// size of `sigLength` field in number of bytes.
constexpr size_t kSigLengthFieldLengthInBytes = 3;
// size of `headerLength` field in number of bytes.
constexpr size_t kHeaderLengthFieldLengthInBytes = 3;
constexpr size_t kMaximumSignatureHeaderFieldLength = 16 * 1024;
constexpr size_t kMaximumCBORHeaderLength = 512 * 1024;
} // namespace
namespace signed_exchange_prologue {
constexpr size_t BeforeFallbackUrl::kEncodedSizeInBytes =
sizeof(kSignedExchangeMagic) + kFallbackUrlLengthFieldSizeInBytes;
size_t Parse2BytesEncodedLength(base::span<const uint8_t> input) {
DCHECK_EQ(input.size(), 2u);
return static_cast<size_t>(input[0]) << 8 | static_cast<size_t>(input[1]);
}
size_t Parse3BytesEncodedLength(base::span<const uint8_t> input) {
DCHECK_EQ(input.size(), 3u);
return static_cast<size_t>(input[0]) << 16 |
static_cast<size_t>(input[1]) << 8 | static_cast<size_t>(input[2]);
}
// static
BeforeFallbackUrl BeforeFallbackUrl::Parse(
base::span<const uint8_t> input,
SignedExchangeDevToolsProxy* devtools_proxy) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"signed_exchange_prologue::BeforeFallbackUrl::Parse");
CHECK_EQ(input.size(), kEncodedSizeInBytes);
const auto magic_string = input.subspan(0, sizeof(kSignedExchangeMagic));
const auto encoded_fallback_url_length_field = input.subspan(
sizeof(kSignedExchangeMagic), kFallbackUrlLengthFieldSizeInBytes);
bool is_valid = true;
if (memcmp(magic_string.data(), kSignedExchangeMagic,
sizeof(kSignedExchangeMagic)) != 0) {
signed_exchange_utils::ReportErrorAndTraceEvent(devtools_proxy,
"Wrong magic string");
is_valid = false;
}
size_t fallback_url_length =
Parse2BytesEncodedLength(encoded_fallback_url_length_field);
return BeforeFallbackUrl(is_valid, fallback_url_length);
}
size_t BeforeFallbackUrl::ComputeFallbackUrlAndAfterLength() const {
return fallback_url_length_ + kSigLengthFieldLengthInBytes +
kHeaderLengthFieldLengthInBytes;
}
// static
FallbackUrlAndAfter FallbackUrlAndAfter::ParseFailedButFallbackUrlAvailable(
GURL fallback_url) {
return FallbackUrlAndAfter(/*is_valid=*/false, std::move(fallback_url),
/*signature_header_field_length=*/0,
/*cbor_header_length=*/0);
}
// static
FallbackUrlAndAfter FallbackUrlAndAfter::Parse(
base::span<const uint8_t> input,
const BeforeFallbackUrl& before_fallback_url,
SignedExchangeDevToolsProxy* devtools_proxy) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"signed_exchange_prologue::FallbackUrlAndAfter::Parse");
if (input.size() < before_fallback_url.fallback_url_length()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy,
"End of stream reached before reading the entire `fallbackUrl`.");
return FallbackUrlAndAfter();
}
base::StringPiece fallback_url_str(
reinterpret_cast<const char*>(input.data()),
before_fallback_url.fallback_url_length());
GURL fallback_url(fallback_url_str);
if (!fallback_url.is_valid()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy, "Failed to parse `fallbackUrl`.");
return FallbackUrlAndAfter();
}
if (!fallback_url.SchemeIs(url::kHttpsScheme)) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy, "`fallbackUrl` in non-https scheme.");
return FallbackUrlAndAfter();
}
if (fallback_url.has_ref()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy, ":url can't have a fragment.");
return FallbackUrlAndAfter();
}
// Note: For the code path after this comment, if parsing failed but
// the `fallbackUrl` parse had succeed, the return value can still be
// used for fallback redirect.
if (!before_fallback_url.is_valid())
return ParseFailedButFallbackUrlAvailable(fallback_url);
if (input.size() < before_fallback_url.ComputeFallbackUrlAndAfterLength()) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy,
"End of stream reached before reading `sigLength` and `headerLength` "
"fields.");
return ParseFailedButFallbackUrlAvailable(fallback_url);
}
const auto encoded_signature_header_field_length = input.subspan(
before_fallback_url.fallback_url_length(), kSigLengthFieldLengthInBytes);
const auto encoded_cbor_header_length = input.subspan(
before_fallback_url.fallback_url_length() + kSigLengthFieldLengthInBytes,
kHeaderLengthFieldLengthInBytes);
size_t signature_header_field_length =
Parse3BytesEncodedLength(encoded_signature_header_field_length);
size_t cbor_header_length =
Parse3BytesEncodedLength(encoded_cbor_header_length);
if (signature_header_field_length > kMaximumSignatureHeaderFieldLength) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy,
base::StringPrintf("Signature header field too long: %zu",
signature_header_field_length));
return ParseFailedButFallbackUrlAvailable(fallback_url);
}
if (cbor_header_length > kMaximumCBORHeaderLength) {
signed_exchange_utils::ReportErrorAndTraceEvent(
devtools_proxy,
base::StringPrintf("CBOR header too long: %zu", cbor_header_length));
return ParseFailedButFallbackUrlAvailable(fallback_url);
}
return FallbackUrlAndAfter(true, fallback_url, signature_header_field_length,
cbor_header_length);
}
size_t FallbackUrlAndAfter::signature_header_field_length() const {
DCHECK(is_valid());
return signature_header_field_length_;
}
size_t FallbackUrlAndAfter::cbor_header_length() const {
DCHECK(is_valid());
return cbor_header_length_;
}
size_t FallbackUrlAndAfter::ComputeFollowingSectionsLength() const {
DCHECK(is_valid());
return signature_header_field_length_ + cbor_header_length_;
}
} // namespace signed_exchange_prologue
} // namespace content