blob: 0989514ef5a5817859eb804c80d0253e3f4bfca6 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/unencoded_digests.h"
#include <optional>
#include <vector>
#include "base/base64.h"
#include "base/containers/contains.h"
#include "net/http/http_response_headers.h"
#include "net/http/structured_headers.h"
#include "services/network/public/cpp/integrity_metadata.h"
#include "services/network/public/mojom/devtools_observer.mojom.h"
#include "url/gurl.h"
namespace network {
namespace {
constexpr char kUnencodedDigest[] = "unencoded-digest";
constexpr char kSha256Label[] = "sha-256";
constexpr char kSha512Label[] = "sha-512";
const size_t kSha256DigestLength = 32;
const size_t kSha512DigestLength = 64;
} // namespace
mojom::UnencodedDigestsPtr ParseUnencodedDigestsFromHeaders(
const net::HttpResponseHeaders& headers) {
auto parsed_headers = mojom::UnencodedDigests::New();
std::optional<std::string> header =
headers.GetNormalizedHeader(kUnencodedDigest);
if (!header) {
return parsed_headers;
}
std::optional<net::structured_headers::Dictionary> dictionary =
net::structured_headers::ParseDictionary(*header);
if (!dictionary) {
parsed_headers->issues.emplace_back(
mojom::UnencodedDigestIssue::kMalformedDictionary);
return parsed_headers;
}
for (const auto& entry : dictionary.value()) {
network::IntegrityMetadata parsed_digest;
size_t expected_length = 0;
if (entry.first == kSha256Label) {
parsed_digest.algorithm = mojom::IntegrityAlgorithm::kSha256;
expected_length = kSha256DigestLength;
} else if (entry.first == kSha512Label) {
parsed_digest.algorithm = mojom::IntegrityAlgorithm::kSha512;
expected_length = kSha512DigestLength;
} else {
// We only accept SHA-256 and SHA-512; skip over digests using unknown
// algorithm tokens.
parsed_headers->issues.emplace_back(
mojom::UnencodedDigestIssue::kUnknownAlgorithm);
continue;
}
// Skip entries that cannot be parsed as byte sequences.
if (entry.second.member_is_inner_list || entry.second.member.empty() ||
!entry.second.member[0].item.is_byte_sequence()) {
parsed_headers->issues.emplace_back(
mojom::UnencodedDigestIssue::kIncorrectDigestType);
continue;
}
// Store the digest after converting it from a string into a vector.
std::string digest_string = entry.second.member[0].item.GetString();
if (digest_string.size() != expected_length) {
parsed_headers->issues.emplace_back(
mojom::UnencodedDigestIssue::kIncorrectDigestLength);
continue;
}
parsed_digest.value.assign(digest_string.begin(), digest_string.end());
parsed_headers->digests.emplace_back(std::move(parsed_digest));
}
return parsed_headers;
}
void ReportUnencodedDigestIssuesToDevtools(
const mojom::UnencodedDigestsPtr& digests,
const raw_ptr<mojom::DevToolsObserver> devtools_observer,
const std::string& devtools_request_id,
const GURL& request_url) {
if (!devtools_observer || devtools_request_id.empty()) {
return;
}
for (const mojom::UnencodedDigestIssue issue : digests->issues) {
devtools_observer->OnUnencodedDigestError(devtools_request_id, request_url,
issue);
}
}
} // namespace network