blob: 4083ba24c8b33d9c7ed3bfed0bd20fb096bdfdd6 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/core/loader/alternate_signed_exchange_resource_info.h"
#include "media/media_buildflags.h"
#include "net/http/http_request_headers.h"
#include "third_party/blink/public/common/web_package/signed_exchange_consts.h"
#include "third_party/blink/public/common/web_package/web_package_request_matcher.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/link_header.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
namespace blink {
namespace {
constexpr char kAlternate[] = "alternate";
constexpr char kAllowedAltSxg[] = "allowed-alt-sxg";
// These accept header values are also defined in
// blink/renderer/platform/loader/fetch/url_loader/fetch_conversion.cc and
// services/network/loader_util.h.
// TODO(horo): Move somewhere and use shared constant value.
const char kDefaultAcceptHeader[] = "*/*";
const char kStylesheetAcceptHeader[] = "text/css,*/*;q=0.1";
#if BUILDFLAG(ENABLE_AV1_DECODER)
constexpr char kImageAcceptHeader[] =
"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
#else
constexpr char kImageAcceptHeader[] =
"image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
#endif
using AlternateSignedExchangeMachingKey =
std::pair<String /* anchor */,
std::pair<String /* variants */, String /* variant_key */>>;
AlternateSignedExchangeMachingKey MakeKey(const String& anchor,
const String& variants,
const String& variant_key) {
// Null string can't be used as a key of HashMap.
return std::make_pair(
anchor.IsNull() ? "" : anchor,
std::make_pair(variants.IsNull() ? "" : variants,
variant_key.IsNull() ? "" : variant_key));
}
void AddAlternateUrlIfValid(
const LinkHeader& header,
HashMap<AlternateSignedExchangeMachingKey, KURL>* alternate_urls) {
if (!header.Valid() || header.Url().IsEmpty() ||
!header.Anchor().has_value() || header.Anchor()->IsEmpty() ||
!EqualIgnoringASCIICase(header.Rel(), kAlternate) ||
header.MimeType() != kSignedExchangeMimeType) {
return;
}
const KURL alternative_url(header.Url());
const KURL anchor_url(*header.Anchor());
if (!alternative_url.IsValid() || !anchor_url.IsValid())
return;
alternate_urls->Set(
MakeKey(*header.Anchor(), header.Variants(), header.VariantKey()),
alternative_url);
}
std::unique_ptr<AlternateSignedExchangeResourceInfo::Entry>
CreateEntryForLinkHeaderIfValid(
const LinkHeader& header,
const HashMap<AlternateSignedExchangeMachingKey, KURL>& alternate_urls) {
if (!header.Valid() || header.Url().IsEmpty() ||
header.HeaderIntegrity().IsEmpty() ||
!EqualIgnoringASCIICase(header.Rel(), kAllowedAltSxg)) {
return nullptr;
}
const KURL anchor_url(header.Url());
if (!anchor_url.IsValid())
return nullptr;
KURL alternative_url;
const auto alternate_urls_it = alternate_urls.find(
MakeKey(header.Url(), header.Variants(), header.VariantKey()));
if (alternate_urls_it != alternate_urls.end())
alternative_url = alternate_urls_it->value;
return std::make_unique<AlternateSignedExchangeResourceInfo::Entry>(
anchor_url, alternative_url, header.HeaderIntegrity(), header.Variants(),
header.VariantKey());
}
} // namespace
std::unique_ptr<AlternateSignedExchangeResourceInfo>
AlternateSignedExchangeResourceInfo::CreateIfValid(
const String& outer_link_header,
const String& inner_link_header) {
HashMap<AlternateSignedExchangeMachingKey, KURL> alternate_urls;
const auto outer_link_headers = LinkHeaderSet(outer_link_header);
for (const auto& header : outer_link_headers) {
AddAlternateUrlIfValid(header, &alternate_urls);
}
EntryMap alternative_resources;
const auto inner_link_headers = LinkHeaderSet(inner_link_header);
for (const auto& header : inner_link_headers) {
auto alt_resource = CreateEntryForLinkHeaderIfValid(header, alternate_urls);
if (!alt_resource)
continue;
auto anchor_url = alt_resource->anchor_url();
auto alternative_resources_it = alternative_resources.find(anchor_url);
if (alternative_resources_it == alternative_resources.end()) {
Vector<std::unique_ptr<Entry>> resource_list;
resource_list.emplace_back(std::move(alt_resource));
alternative_resources.Set(anchor_url, std::move(resource_list));
} else {
alternative_resources_it->value.emplace_back(std::move(alt_resource));
}
}
if (alternative_resources.IsEmpty())
return nullptr;
return std::make_unique<AlternateSignedExchangeResourceInfo>(
std::move(alternative_resources));
}
AlternateSignedExchangeResourceInfo::AlternateSignedExchangeResourceInfo(
EntryMap alternative_resources)
: alternative_resources_(std::move(alternative_resources)) {}
AlternateSignedExchangeResourceInfo::Entry*
AlternateSignedExchangeResourceInfo::FindMatchingEntry(
const KURL& url,
base::Optional<ResourceType> resource_type,
const Vector<String>& languages) const {
const char* accept_header = kDefaultAcceptHeader;
if (resource_type == ResourceType::kCSSStyleSheet) {
accept_header = kStylesheetAcceptHeader;
} else if (resource_type == ResourceType::kImage) {
accept_header = kImageAcceptHeader;
}
return FindMatchingEntry(url, accept_header, languages);
}
AlternateSignedExchangeResourceInfo::Entry*
AlternateSignedExchangeResourceInfo::FindMatchingEntry(
const KURL& url,
mojom::blink::RequestContextType request_context,
const Vector<String>& languages) const {
const char* accept_header = kDefaultAcceptHeader;
if (request_context == mojom::blink::RequestContextType::STYLE) {
accept_header = kStylesheetAcceptHeader;
} else if (request_context == mojom::blink::RequestContextType::IMAGE) {
accept_header = kImageAcceptHeader;
}
return FindMatchingEntry(url, accept_header, languages);
}
AlternateSignedExchangeResourceInfo::Entry*
AlternateSignedExchangeResourceInfo::FindMatchingEntry(
const KURL& url,
const char* accept_header,
const Vector<String>& languages) const {
const auto it = alternative_resources_.find(url);
if (it == alternative_resources_.end())
return nullptr;
const Vector<std::unique_ptr<Entry>>& entries = it->value;
DCHECK(!entries.IsEmpty());
if (entries[0]->variants().IsNull())
return entries[0].get();
const std::string variants = entries[0]->variants().Utf8();
std::vector<std::string> variant_keys_list;
for (const auto& entry : entries) {
variant_keys_list.emplace_back(entry->variant_key().Utf8());
}
std::string accept_langs;
for (const auto& language : languages) {
if (!accept_langs.empty())
accept_langs += ",";
accept_langs += language.Utf8();
}
net::HttpRequestHeaders request_headers;
request_headers.SetHeader(net::HttpRequestHeaders::kAccept, accept_header);
WebPackageRequestMatcher matcher(request_headers, accept_langs);
const auto variant_keys_list_it =
matcher.FindBestMatchingVariantKey(variants, variant_keys_list);
if (variant_keys_list_it == variant_keys_list.end())
return nullptr;
return (entries.begin() + (variant_keys_list_it - variant_keys_list.begin()))
->get();
}
} // namespace blink