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