| // Copyright 2017 The Chromium Authors |
| // 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/base_fetch_context.h" |
| |
| #include "base/command_line.h" |
| #include "net/http/structured_headers.h" |
| #include "services/network/public/cpp/client_hints.h" |
| #include "services/network/public/cpp/request_mode.h" |
| #include "third_party/blink/public/common/client_hints/client_hints.h" |
| #include "third_party/blink/public/common/device_memory/approximated_device_memory.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/permissions_policy/permissions_policy.h" |
| #include "third_party/blink/public/common/switches.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" |
| #include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h" |
| #include "third_party/blink/public/platform/web_content_settings_client.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/inspector/identifiers_factory.h" |
| #include "third_party/blink/renderer/core/loader/frame_client_hints_preferences_context.h" |
| #include "third_party/blink/renderer/core/loader/idna_util.h" |
| #include "third_party/blink/renderer/core/loader/subresource_filter.h" |
| #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/loader/cors/cors.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h" |
| #include "third_party/blink/renderer/platform/network/network_state_notifier.h" |
| #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_policy.h" |
| |
| namespace { |
| |
| // Creates a serialized AtomicString header value out of the input string, using |
| // structured headers as described in |
| // https://www.rfc-editor.org/rfc/rfc8941.html. |
| const AtomicString SerializeStringHeader(std::string str) { |
| std::string output; |
| |
| // See https://crbug.com/1416925. |
| if (str.empty() && |
| !base::FeatureList::IsEnabled( |
| blink::features::kQuoteEmptySecChUaStringHeadersConsistently)) { |
| return AtomicString(output.c_str()); |
| } |
| |
| output = |
| net::structured_headers::SerializeItem(net::structured_headers::Item(str)) |
| .value_or(std::string()); |
| |
| return AtomicString(output.c_str()); |
| } |
| |
| // Creates a serialized AtomicString header value out of the input boolean, |
| // using structured headers as described in |
| // https://www.rfc-editor.org/rfc/rfc8941.html. |
| const AtomicString SerializeBoolHeader(const bool value) { |
| const std::string output = net::structured_headers::SerializeItem( |
| net::structured_headers::Item(value)) |
| .value_or(std::string()); |
| |
| return AtomicString(output.c_str()); |
| } |
| |
| void SetHttpHeader(network::mojom::blink::WebClientHintsType hints_type, |
| const AtomicString& value, |
| blink::ResourceRequest& request) { |
| std::string header_name = network::GetClientHintToNameMap().at(hints_type); |
| request.SetHttpHeaderField( |
| AtomicString(reinterpret_cast<const LChar*>(header_name.data()), |
| header_name.size()), |
| value); |
| } |
| |
| } // namespace |
| |
| namespace blink { |
| |
| std::optional<ResourceRequestBlockedReason> BaseFetchContext::CanRequest( |
| ResourceType type, |
| const ResourceRequest& resource_request, |
| const KURL& url, |
| const ResourceLoaderOptions& options, |
| ReportingDisposition reporting_disposition, |
| base::optional_ref<const ResourceRequest::RedirectInfo> redirect_info) |
| const { |
| std::optional<ResourceRequestBlockedReason> blocked_reason = |
| CanRequestInternal(type, resource_request, url, options, |
| reporting_disposition, redirect_info); |
| if (blocked_reason && |
| reporting_disposition == ReportingDisposition::kReport) { |
| DispatchDidBlockRequest(resource_request, options, blocked_reason.value(), |
| type); |
| } |
| return blocked_reason; |
| } |
| |
| std::optional<ResourceRequestBlockedReason> |
| BaseFetchContext::CanRequestBasedOnSubresourceFilterOnly( |
| ResourceType type, |
| const ResourceRequest& resource_request, |
| const KURL& url, |
| const ResourceLoaderOptions& options, |
| ReportingDisposition reporting_disposition, |
| base::optional_ref<const ResourceRequest::RedirectInfo> redirect_info) |
| const { |
| auto* subresource_filter = GetSubresourceFilter(); |
| if (subresource_filter && |
| !subresource_filter->AllowLoad(url, resource_request.GetRequestContext(), |
| reporting_disposition)) { |
| if (reporting_disposition == ReportingDisposition::kReport) { |
| DispatchDidBlockRequest(resource_request, options, |
| ResourceRequestBlockedReason::kSubresourceFilter, |
| type); |
| } |
| return ResourceRequestBlockedReason::kSubresourceFilter; |
| } |
| |
| return std::nullopt; |
| } |
| |
| bool BaseFetchContext::CalculateIfAdSubresource( |
| const ResourceRequestHead& request, |
| base::optional_ref<const KURL> alias_url, |
| ResourceType type, |
| const FetchInitiatorInfo& initiator_info) { |
| // A derived class should override this if they have more signals than just |
| // the SubresourceFilter. |
| SubresourceFilter* filter = GetSubresourceFilter(); |
| const KURL& url = alias_url.has_value() ? alias_url.value() : request.Url(); |
| |
| return request.IsAdResource() || |
| (filter && filter->IsAdResource(url, request.GetRequestContext())); |
| } |
| |
| // TODO(https://crbug.com/1469830) Refactor the strings into some sort of |
| // context object |
| void BaseFetchContext::AddClientHintsIfNecessary( |
| const ClientHintsPreferences& hints_preferences, |
| const url::Origin& resource_origin, |
| bool is_1p_origin, |
| std::optional<UserAgentMetadata> ua, |
| const PermissionsPolicy* policy, |
| base::optional_ref<const ClientHintImageInfo> image_info, |
| base::optional_ref<const WTF::AtomicString> prefers_color_scheme, |
| base::optional_ref<const WTF::AtomicString> prefers_reduced_motion, |
| base::optional_ref<const WTF::AtomicString> prefers_reduced_transparency, |
| ResourceRequest& request) { |
| // If the feature is enabled, then client hints are allowed only on secure |
| // URLs. |
| if (!ClientHintsPreferences::IsClientHintsAllowed(request.Url())) |
| return; |
| |
| // Sec-CH-UA is special: we always send the header to all origins that are |
| // eligible for client hints (e.g. secure transport, JavaScript enabled). |
| // |
| // https://github.com/WICG/ua-client-hints |
| // |
| // One exception, however, is that a custom UA is sometimes set without |
| // specifying accomponying client hints, in which case we disable sending |
| // them. |
| using network::mojom::blink::WebClientHintsType; |
| if (ua) { |
| // ShouldSendClientHint is called to make sure UA is controlled by |
| // Permissions Policy. |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUA, hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUA, |
| AtomicString(ua->SerializeBrandMajorVersionList().c_str()), |
| request); |
| } |
| |
| // We also send Sec-CH-UA-Mobile to all hints. It is a one-bit header |
| // identifying if the browser has opted for a "mobile" experience. |
| // ShouldSendClientHint is called to make sure it's controlled by |
| // PermissionsPolicy. |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAMobile, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAMobile, |
| SerializeBoolHeader(ua->mobile), request); |
| } |
| } |
| |
| // If the frame is detached, then don't send any hints other than UA. |
| if (!policy) |
| return; |
| |
| // The next 4 hints should be enabled if we're allowing legacy hints to third |
| // parties, or if PermissionsPolicy delegation says they are allowed. |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kDeviceMemory_DEPRECATED, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kDeviceMemory_DEPRECATED, |
| AtomicString(String::Number( |
| ApproximatedDeviceMemory::GetApproximatedDeviceMemory())), |
| request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kDeviceMemory, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kDeviceMemory, |
| AtomicString(String::Number( |
| ApproximatedDeviceMemory::GetApproximatedDeviceMemory())), |
| request); |
| } |
| |
| // These hints only make sense if the image info is available |
| if (image_info.has_value()) { |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kDpr_DEPRECATED, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kDpr_DEPRECATED, |
| AtomicString(String::Number(image_info->dpr)), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kDpr, hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kDpr, |
| AtomicString(String::Number(image_info->dpr)), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kViewportWidth_DEPRECATED, |
| hints_preferences) && |
| image_info->viewport_width) { |
| SetHttpHeader( |
| WebClientHintsType::kViewportWidth_DEPRECATED, |
| AtomicString(String::Number(image_info->viewport_width.value())), |
| request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kViewportWidth, |
| hints_preferences) && |
| image_info->viewport_width) { |
| SetHttpHeader( |
| WebClientHintsType::kViewportWidth, |
| AtomicString(String::Number(image_info->viewport_width.value())), |
| request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kViewportHeight, |
| hints_preferences) && |
| image_info->viewport_height) { |
| SetHttpHeader( |
| WebClientHintsType::kViewportHeight, |
| AtomicString(String::Number(image_info->viewport_height.value())), |
| request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kResourceWidth_DEPRECATED, |
| hints_preferences)) { |
| if (image_info->resource_width) { |
| float physical_width = |
| image_info->resource_width.value() * image_info->dpr; |
| SetHttpHeader(WebClientHintsType::kResourceWidth_DEPRECATED, |
| AtomicString(String::Number(ceil(physical_width))), |
| request); |
| } |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kResourceWidth, |
| hints_preferences)) { |
| if (image_info->resource_width) { |
| float physical_width = |
| image_info->resource_width.value() * image_info->dpr; |
| SetHttpHeader(WebClientHintsType::kResourceWidth, |
| AtomicString(String::Number(ceil(physical_width))), |
| request); |
| } |
| } |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kRtt_DEPRECATED, |
| hints_preferences)) { |
| std::optional<base::TimeDelta> http_rtt = |
| GetNetworkStateNotifier().GetWebHoldbackHttpRtt(); |
| if (!http_rtt) { |
| http_rtt = GetNetworkStateNotifier().HttpRtt(); |
| } |
| |
| uint32_t rtt = |
| GetNetworkStateNotifier().RoundRtt(request.Url().Host(), http_rtt); |
| SetHttpHeader(WebClientHintsType::kRtt_DEPRECATED, |
| AtomicString(String::Number(rtt)), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kDownlink_DEPRECATED, |
| hints_preferences)) { |
| std::optional<double> throughput_mbps = |
| GetNetworkStateNotifier().GetWebHoldbackDownlinkThroughputMbps(); |
| if (!throughput_mbps) { |
| throughput_mbps = GetNetworkStateNotifier().DownlinkThroughputMbps(); |
| } |
| |
| double mbps = GetNetworkStateNotifier().RoundMbps(request.Url().Host(), |
| throughput_mbps); |
| SetHttpHeader(WebClientHintsType::kDownlink_DEPRECATED, |
| AtomicString(String::Number(mbps)), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kEct_DEPRECATED, |
| hints_preferences)) { |
| std::optional<WebEffectiveConnectionType> holdback_ect = |
| GetNetworkStateNotifier().GetWebHoldbackEffectiveType(); |
| if (!holdback_ect) |
| holdback_ect = GetNetworkStateNotifier().EffectiveType(); |
| |
| SetHttpHeader( |
| WebClientHintsType::kEct_DEPRECATED, |
| AtomicString(NetworkStateNotifier::EffectiveConnectionTypeToString( |
| holdback_ect.value())), |
| request); |
| } |
| |
| // Only send User Agent hints if the info is available |
| if (ua) { |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAArch, hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAArch, |
| SerializeStringHeader(ua->architecture), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAPlatform, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAPlatform, |
| SerializeStringHeader(ua->platform), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAPlatformVersion, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAPlatformVersion, |
| SerializeStringHeader(ua->platform_version), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAModel, hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAModel, |
| SerializeStringHeader(ua->model), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAFullVersion, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAFullVersion, |
| SerializeStringHeader(ua->full_version), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAFullVersionList, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAFullVersionList, |
| AtomicString(ua->SerializeBrandFullVersionList().c_str()), |
| request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUABitness, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUABitness, |
| SerializeStringHeader(ua->bitness), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kUAWoW64, hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAWoW64, |
| SerializeBoolHeader(ua->wow64), request); |
| } |
| |
| if (ShouldSendClientHint( |
| policy, resource_origin, is_1p_origin, |
| network::mojom::blink::WebClientHintsType::kUAFormFactor, |
| hints_preferences)) { |
| SetHttpHeader(WebClientHintsType::kUAFormFactor, |
| AtomicString(ua->SerializeFormFactor().c_str()), request); |
| } |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kPrefersColorScheme, |
| hints_preferences) && |
| prefers_color_scheme.has_value()) { |
| SetHttpHeader(WebClientHintsType::kPrefersColorScheme, |
| prefers_color_scheme.value(), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kSaveData, hints_preferences)) { |
| if (GetNetworkStateNotifier().SaveDataEnabled()) { |
| SetHttpHeader(WebClientHintsType::kSaveData, AtomicString("on"), request); |
| } |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kPrefersReducedMotion, |
| hints_preferences) && |
| prefers_reduced_motion.has_value()) { |
| SetHttpHeader(WebClientHintsType::kPrefersReducedMotion, |
| prefers_reduced_motion.value(), request); |
| } |
| |
| if (ShouldSendClientHint(policy, resource_origin, is_1p_origin, |
| WebClientHintsType::kPrefersReducedTransparency, |
| hints_preferences) && |
| prefers_reduced_transparency.has_value()) { |
| SetHttpHeader(WebClientHintsType::kPrefersReducedTransparency, |
| prefers_reduced_transparency.value(), request); |
| } |
| } |
| |
| void BaseFetchContext::PrintAccessDeniedMessage(const KURL& url) const { |
| if (url.IsNull()) |
| return; |
| |
| String message; |
| if (Url().IsNull()) { |
| message = "Unsafe attempt to load URL " + url.ElidedString() + '.'; |
| } else if (url.IsLocalFile() || Url().IsLocalFile()) { |
| message = "Unsafe attempt to load URL " + url.ElidedString() + |
| " from frame with URL " + Url().ElidedString() + |
| ". 'file:' URLs are treated as unique security origins.\n"; |
| } else { |
| message = "Unsafe attempt to load URL " + url.ElidedString() + |
| " from frame with URL " + Url().ElidedString() + |
| ". Domains, protocols and ports must match.\n"; |
| } |
| |
| console_logger_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kError, message)); |
| } |
| |
| std::optional<ResourceRequestBlockedReason> |
| BaseFetchContext::CheckCSPForRequest( |
| mojom::blink::RequestContextType request_context, |
| network::mojom::RequestDestination request_destination, |
| const KURL& url, |
| const ResourceLoaderOptions& options, |
| ReportingDisposition reporting_disposition, |
| const KURL& url_before_redirects, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| return CheckCSPForRequestInternal( |
| request_context, request_destination, url, options, reporting_disposition, |
| url_before_redirects, redirect_status, |
| ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly); |
| } |
| |
| std::optional<ResourceRequestBlockedReason> |
| BaseFetchContext::CheckCSPForRequestInternal( |
| mojom::blink::RequestContextType request_context, |
| network::mojom::RequestDestination request_destination, |
| const KURL& url, |
| const ResourceLoaderOptions& options, |
| ReportingDisposition reporting_disposition, |
| const KURL& url_before_redirects, |
| ResourceRequest::RedirectStatus redirect_status, |
| ContentSecurityPolicy::CheckHeaderType check_header_type) const { |
| if (options.content_security_policy_option == |
| network::mojom::CSPDisposition::DO_NOT_CHECK) { |
| return std::nullopt; |
| } |
| |
| ContentSecurityPolicy* csp = |
| GetContentSecurityPolicyForWorld(options.world_for_csp.get()); |
| if (csp && |
| !csp->AllowRequest(request_context, request_destination, url, |
| options.content_security_policy_nonce, |
| options.integrity_metadata, options.parser_disposition, |
| url_before_redirects, redirect_status, |
| reporting_disposition, check_header_type)) { |
| return ResourceRequestBlockedReason::kCSP; |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<ResourceRequestBlockedReason> |
| BaseFetchContext::CanRequestInternal( |
| ResourceType type, |
| const ResourceRequest& resource_request, |
| const KURL& url, |
| const ResourceLoaderOptions& options, |
| ReportingDisposition reporting_disposition, |
| base::optional_ref<const ResourceRequest::RedirectInfo> redirect_info) |
| const { |
| if (GetResourceFetcherProperties().IsDetached()) { |
| if (!resource_request.GetKeepalive() || !redirect_info.has_value()) { |
| return ResourceRequestBlockedReason::kOther; |
| } |
| } |
| |
| if (ShouldBlockRequestByInspector(resource_request.Url())) |
| return ResourceRequestBlockedReason::kInspector; |
| |
| scoped_refptr<const SecurityOrigin> origin = |
| resource_request.RequestorOrigin(); |
| |
| const auto request_mode = resource_request.GetMode(); |
| // On navigation cases, Context().GetSecurityOrigin() may return nullptr, so |
| // the request's origin may be nullptr. |
| // TODO(yhirano): Figure out if it's actually fine. |
| DCHECK(request_mode == network::mojom::RequestMode::kNavigate || origin); |
| if (request_mode != network::mojom::RequestMode::kNavigate && |
| !resource_request.CanDisplay(url)) { |
| if (reporting_disposition == ReportingDisposition::kReport) { |
| console_logger_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kJavaScript, |
| mojom::ConsoleMessageLevel::kError, |
| "Not allowed to load local resource: " + url.GetString())); |
| } |
| RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::requestResource URL was not " |
| "allowed by SecurityOrigin::CanDisplay"; |
| return ResourceRequestBlockedReason::kOther; |
| } |
| |
| if (request_mode == network::mojom::RequestMode::kSameOrigin && |
| cors::CalculateCorsFlag(url, origin.get(), |
| resource_request.IsolatedWorldOrigin().get(), |
| request_mode)) { |
| PrintAccessDeniedMessage(url); |
| return ResourceRequestBlockedReason::kOrigin; |
| } |
| |
| // User Agent CSS stylesheets should only support loading images and should be |
| // restricted to data urls. |
| if (options.initiator_info.name == fetch_initiator_type_names::kUacss) { |
| if (type == ResourceType::kImage && url.ProtocolIsData()) { |
| return std::nullopt; |
| } |
| return ResourceRequestBlockedReason::kOther; |
| } |
| |
| mojom::blink::RequestContextType request_context = |
| resource_request.GetRequestContext(); |
| network::mojom::RequestDestination request_destination = |
| resource_request.GetRequestDestination(); |
| |
| const KURL& url_before_redirects = |
| redirect_info.has_value() ? redirect_info->original_url : url; |
| const ResourceRequestHead::RedirectStatus redirect_status = |
| redirect_info.has_value() |
| ? ResourceRequestHead::RedirectStatus::kFollowedRedirect |
| : ResourceRequestHead::RedirectStatus::kNoRedirect; |
| // We check the 'report-only' headers before upgrading the request (in |
| // populateResourceRequest). We check the enforced headers here to ensure we |
| // block things we ought to block. |
| if (CheckCSPForRequestInternal( |
| request_context, request_destination, url, options, |
| reporting_disposition, url_before_redirects, redirect_status, |
| ContentSecurityPolicy::CheckHeaderType::kCheckEnforce) == |
| ResourceRequestBlockedReason::kCSP) { |
| return ResourceRequestBlockedReason::kCSP; |
| } |
| |
| if (type == ResourceType::kScript) { |
| if (!AllowScriptFromSource(url)) { |
| // TODO(estark): Use a different ResourceRequestBlockedReason here, since |
| // this check has nothing to do with CSP. https://crbug.com/600795 |
| return ResourceRequestBlockedReason::kCSP; |
| } |
| } |
| |
| // SVG Images have unique security rules that prevent all subresource requests |
| // except for data urls. |
| if (IsSVGImageChromeClient() && !url.ProtocolIsData()) |
| return ResourceRequestBlockedReason::kOrigin; |
| |
| // data: URL is deprecated in SVGUseElement. |
| if (RuntimeEnabledFeatures::RemoveDataUrlInSvgUseEnabled() && |
| options.initiator_info.name == fetch_initiator_type_names::kUse && |
| url.ProtocolIsData() && |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| blink::switches::kDataUrlInSvgUseEnabled)) { |
| PrintAccessDeniedMessage(url); |
| return ResourceRequestBlockedReason::kOrigin; |
| } |
| |
| // Measure the number of embedded-credential ('http://user:password@...') |
| // resources embedded as subresources. |
| const FetchClientSettingsObject& fetch_client_settings_object = |
| GetResourceFetcherProperties().GetFetchClientSettingsObject(); |
| const SecurityOrigin* embedding_origin = |
| fetch_client_settings_object.GetSecurityOrigin(); |
| DCHECK(embedding_origin); |
| if (ShouldBlockFetchAsCredentialedSubresource(resource_request, url)) |
| return ResourceRequestBlockedReason::kOrigin; |
| |
| // Check for mixed content. We do this second-to-last so that when folks block |
| // mixed content via CSP, they don't get a mixed content warning, but a CSP |
| // warning instead. |
| if (ShouldBlockFetchByMixedContentCheck( |
| request_context, resource_request.GetTargetAddressSpace(), |
| redirect_info, url, reporting_disposition, |
| resource_request.GetDevToolsId())) { |
| return ResourceRequestBlockedReason::kMixedContent; |
| } |
| |
| if (url.PotentiallyDanglingMarkup() && url.ProtocolIsInHTTPFamily()) { |
| CountDeprecation(WebFeature::kCanRequestURLHTTPContainingNewline); |
| return ResourceRequestBlockedReason::kOther; |
| } |
| |
| // Let the client have the final say into whether or not the load should |
| // proceed. |
| if (GetSubresourceFilter()) { |
| if (!GetSubresourceFilter()->AllowLoad(url, request_context, |
| reporting_disposition)) { |
| return ResourceRequestBlockedReason::kSubresourceFilter; |
| } |
| } |
| |
| // Warn if the resource URL's hostname contains IDNA deviation characters. |
| // Only warn if the resource URL's origin is different than its requestor |
| // (we don't want to warn for <img src="faß.de/image.img"> on faß.de). |
| // TODO(crbug.com/1396475): Remove once Non-Transitional mode is shipped. |
| if (!resource_request.RequestorOrigin()->IsSameOriginWith( |
| SecurityOrigin::Create(url).get()) && |
| url.HasIDNA2008DeviationCharacter()) { |
| String message = GetConsoleWarningForIDNADeviationCharacters(url); |
| if (!message.empty()) { |
| console_logger_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kWarning, message)); |
| UseCounter::Count( |
| GetExecutionContext(), |
| WebFeature::kIDNA2008DeviationCharacterInHostnameOfSubresource); |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| bool BaseFetchContext::ShouldSendClientHint( |
| const PermissionsPolicy* policy, |
| const url::Origin& resource_origin, |
| bool is_1p_origin, |
| network::mojom::blink::WebClientHintsType type, |
| const ClientHintsPreferences& hints_preferences) const { |
| // For subresource requests, sending the hint in the fetch request based on |
| // the permissions policy. |
| if ((!policy || |
| !policy->IsFeatureEnabledForOrigin( |
| GetClientHintToPolicyFeatureMap().at(type), resource_origin))) { |
| return false; |
| } |
| |
| return IsClientHintSentByDefault(type) || hints_preferences.ShouldSend(type); |
| } |
| |
| void BaseFetchContext::Trace(Visitor* visitor) const { |
| visitor->Trace(fetcher_properties_); |
| visitor->Trace(console_logger_); |
| FetchContext::Trace(visitor); |
| } |
| |
| } // namespace blink |