#include "services/network/sec_header_helpers.h"
#include <algorithm>
#include <string>
#include "base/feature_list.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/http/http_request_headers.h"
#include "net/url_request/url_request.h"
#include "services/network/public/cpp/cors/origin_access_list.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/initiator_lock_compatibility.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/request_destination.h"
#include "services/network/public/cpp/request_mode.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace network {
namespace {
const char kSecFetchMode[] = "Sec-Fetch-Mode";
const char kSecFetchSite[] = "Sec-Fetch-Site";
const char kSecFetchUser[] = "Sec-Fetch-User";
const char kSecFetchDest[] = "Sec-Fetch-Dest";
enum class SecFetchSiteValue {
kNoOrigin,
kSameOrigin,
kSameSite,
kCrossSite,
};
const char* GetSecFetchSiteHeaderString(const SecFetchSiteValue& value) {
switch (value) {
case SecFetchSiteValue::kNoOrigin:
return "none";
case SecFetchSiteValue::kSameOrigin:
return "same-origin";
case SecFetchSiteValue::kSameSite:
return "same-site";
case SecFetchSiteValue::kCrossSite:
return "cross-site";
}
}
SecFetchSiteValue GetHeaderValueForTargetAndInitiator(
const GURL& target_url,
const url::Origin& initiator) {
url::Origin target_origin = url::Origin::Create(target_url);
if (target_origin == initiator)
return SecFetchSiteValue::kSameOrigin;
if (initiator.scheme() == target_origin.scheme() &&
net::registry_controlled_domains::SameDomainOrHost(
initiator, target_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
return SecFetchSiteValue::kSameSite;
}
return SecFetchSiteValue::kCrossSite;
}
SecFetchSiteValue GetHeaderValueForRequest(
net::URLRequest* request,
const GURL* pending_redirect_url,
const mojom::URLLoaderFactoryParams& factory_params,
const cors::OriginAccessList& origin_access_list) {
if (!request->initiator().has_value()) {
DCHECK_EQ(factory_params.process_id, mojom::kBrowserProcessId);
return SecFetchSiteValue::kNoOrigin;
}
const url::Origin& initiator = request->initiator().value();
if (factory_params.unsafe_non_webby_initiator) {
cors::OriginAccessList::AccessState access_state =
origin_access_list.CheckAccessState(initiator, request->url());
bool is_privileged =
(access_state == cors::OriginAccessList::AccessState::kAllowed);
return is_privileged ? SecFetchSiteValue::kNoOrigin
: SecFetchSiteValue::kCrossSite;
}
auto header_value = SecFetchSiteValue::kSameOrigin;
for (const GURL& target_url : request->url_chain()) {
header_value = std::max(header_value, GetHeaderValueForTargetAndInitiator(
target_url, initiator));
}
if (pending_redirect_url) {
header_value = std::max(
header_value,
GetHeaderValueForTargetAndInitiator(*pending_redirect_url, initiator));
}
return header_value;
}
void SetSecFetchSiteHeader(net::URLRequest* request,
const GURL* pending_redirect_url,
const mojom::URLLoaderFactoryParams& factory_params,
const cors::OriginAccessList& origin_access_list) {
SecFetchSiteValue header_value = GetHeaderValueForRequest(
request, pending_redirect_url, factory_params, origin_access_list);
request->SetExtraRequestHeaderByName(
kSecFetchSite, GetSecFetchSiteHeaderString(header_value),
true);
}
void SetSecFetchModeHeader(net::URLRequest* request,
network::mojom::RequestMode mode) {
std::string header_value = RequestModeToString(mode);
request->SetExtraRequestHeaderByName(kSecFetchMode, header_value, false);
}
void SetSecFetchUserHeader(net::URLRequest* request, bool has_user_activation) {
if (has_user_activation)
request->SetExtraRequestHeaderByName(kSecFetchUser, "?1", true);
else
request->RemoveRequestHeaderByName(kSecFetchUser);
}
void SetSecFetchDestHeader(net::URLRequest* request,
network::mojom::RequestDestination dest) {
std::string header_value = dest == mojom::RequestDestination::kEmpty
? "empty"
: RequestDestinationToString(dest);
request->SetExtraRequestHeaderByName(kSecFetchDest, header_value, true);
}
}
#ifdef OHOS_NETWORK_LOAD
std::map<std::string, std::string> GetFetchMetadataHeaders(
const GURL& target_url,
network::mojom::RequestMode mode,
bool has_user_activation,
network::mojom::RequestDestination dest,
const absl::optional<url::Origin>& initiator) {
std::map<std::string, std::string> headers;
if (!IsUrlPotentiallyTrustworthy(target_url))
return headers;
auto header_value = SecFetchSiteValue::kSameOrigin;
if (!initiator.has_value()) {
header_value = SecFetchSiteValue::kNoOrigin;
} else {
header_value = std::max(header_value, GetHeaderValueForTargetAndInitiator(
target_url, initiator.value()));
}
headers[kSecFetchSite] = GetSecFetchSiteHeaderString(header_value);
headers[kSecFetchMode] = RequestModeToString(mode);
if (has_user_activation)
headers[kSecFetchUser] = "?1";
std::string destination_value = dest == mojom::RequestDestination::kEmpty
? "empty"
: RequestDestinationToString(dest);
headers[kSecFetchDest] = destination_value;
return headers;
}
#endif
void SetFetchMetadataHeaders(
net::URLRequest* request,
network::mojom::RequestMode mode,
bool has_user_activation,
network::mojom::RequestDestination dest,
const GURL* pending_redirect_url,
const mojom::URLLoaderFactoryParams& factory_params,
const cors::OriginAccessList& origin_access_list) {
DCHECK(request);
DCHECK_NE(0u, request->url_chain().size());
const GURL& target_url =
pending_redirect_url ? *pending_redirect_url : request->url();
if (!IsUrlPotentiallyTrustworthy(target_url))
return;
SetSecFetchSiteHeader(request, pending_redirect_url, factory_params,
origin_access_list);
SetSecFetchModeHeader(request, mode);
SetSecFetchUserHeader(request, has_user_activation);
SetSecFetchDestHeader(request, dest);
}
void MaybeRemoveSecHeaders(net::URLRequest* request,
const GURL& pending_redirect_url) {
DCHECK(request);
if (IsUrlPotentiallyTrustworthy(request->url()) &&
!IsUrlPotentiallyTrustworthy(pending_redirect_url)) {
const net::HttpRequestHeaders::HeaderVector request_headers =
request->extra_request_headers().GetHeaderVector();
for (const auto& header : request_headers) {
if (StartsWith(header.key, "sec-ch-",
base::CompareCase::INSENSITIVE_ASCII) ||
StartsWith(header.key, "sec-fetch-",
base::CompareCase::INSENSITIVE_ASCII)) {
request->RemoveRequestHeaderByName(header.key);
}
}
}
}
}