1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/loader/base_fetch_context.h"
6
7 #include "services/network/public/cpp/request_mode.h"
8 #include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h"
9 #include "third_party/blink/public/platform/web_content_settings_client.h"
10 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
11 #include "third_party/blink/renderer/core/frame/settings.h"
12 #include "third_party/blink/renderer/core/frame/web_feature.h"
13 #include "third_party/blink/renderer/core/inspector/console_message.h"
14 #include "third_party/blink/renderer/core/loader/previews_resource_loading_hints.h"
15 #include "third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.h"
16 #include "third_party/blink/renderer/core/loader/subresource_filter.h"
17 #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
18 #include "third_party/blink/renderer/platform/heap/heap.h"
19 #include "third_party/blink/renderer/platform/loader/cors/cors.h"
20 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
21 #include "third_party/blink/renderer/platform/loader/fetch/resource.h"
22 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
23 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
24 #include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h"
25 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
26 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
27
28 namespace blink {
29
CanRequest(ResourceType type,const ResourceRequest & resource_request,const KURL & url,const ResourceLoaderOptions & options,ReportingDisposition reporting_disposition,const Vector<KURL> & redirect_chain) const30 base::Optional<ResourceRequestBlockedReason> BaseFetchContext::CanRequest(
31 ResourceType type,
32 const ResourceRequest& resource_request,
33 const KURL& url,
34 const ResourceLoaderOptions& options,
35 ReportingDisposition reporting_disposition,
36 const Vector<KURL>& redirect_chain) const {
37 base::Optional<ResourceRequestBlockedReason> blocked_reason =
38 CanRequestInternal(type, resource_request, url, options,
39 reporting_disposition, redirect_chain);
40 if (blocked_reason &&
41 reporting_disposition == ReportingDisposition::kReport) {
42 DispatchDidBlockRequest(resource_request, options.initiator_info,
43 blocked_reason.value(), type);
44 }
45 return blocked_reason;
46 }
47
CalculateIfAdSubresource(const ResourceRequest & request,ResourceType type)48 bool BaseFetchContext::CalculateIfAdSubresource(const ResourceRequest& request,
49 ResourceType type) {
50 // A base class should override this is they have more signals than just the
51 // SubresourceFilter.
52 SubresourceFilter* filter = GetSubresourceFilter();
53
54 return request.IsAdResource() ||
55 (filter &&
56 filter->IsAdResource(request.Url(), request.GetRequestContext()));
57 }
58
SendConversionRequestInsteadOfRedirecting(const KURL & url,const Vector<KURL> & redirect_chain,ReportingDisposition reporting_disposition) const59 bool BaseFetchContext::SendConversionRequestInsteadOfRedirecting(
60 const KURL& url,
61 const Vector<KURL>& redirect_chain,
62 ReportingDisposition reporting_disposition) const {
63 return false;
64 }
65
PrintAccessDeniedMessage(const KURL & url) const66 void BaseFetchContext::PrintAccessDeniedMessage(const KURL& url) const {
67 if (url.IsNull())
68 return;
69
70 String message;
71 if (Url().IsNull()) {
72 message = "Unsafe attempt to load URL " + url.ElidedString() + '.';
73 } else if (url.IsLocalFile() || Url().IsLocalFile()) {
74 message = "Unsafe attempt to load URL " + url.ElidedString() +
75 " from frame with URL " + Url().ElidedString() +
76 ". 'file:' URLs are treated as unique security origins.\n";
77 } else {
78 message = "Unsafe attempt to load URL " + url.ElidedString() +
79 " from frame with URL " + Url().ElidedString() +
80 ". Domains, protocols and ports must match.\n";
81 }
82
83 AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
84 mojom::ConsoleMessageSource::kSecurity,
85 mojom::ConsoleMessageLevel::kError, message));
86 }
87
88 base::Optional<ResourceRequestBlockedReason>
CheckCSPForRequest(mojom::RequestContextType request_context,const KURL & url,const ResourceLoaderOptions & options,ReportingDisposition reporting_disposition,const KURL & url_before_redirects,ResourceRequest::RedirectStatus redirect_status) const89 BaseFetchContext::CheckCSPForRequest(
90 mojom::RequestContextType request_context,
91 const KURL& url,
92 const ResourceLoaderOptions& options,
93 ReportingDisposition reporting_disposition,
94 const KURL& url_before_redirects,
95 ResourceRequest::RedirectStatus redirect_status) const {
96 return CheckCSPForRequestInternal(
97 request_context, url, options, reporting_disposition,
98 url_before_redirects, redirect_status,
99 ContentSecurityPolicy::CheckHeaderType::kCheckReportOnly);
100 }
101
102 base::Optional<ResourceRequestBlockedReason>
CheckCSPForRequestInternal(mojom::RequestContextType request_context,const KURL & url,const ResourceLoaderOptions & options,ReportingDisposition reporting_disposition,const KURL & url_before_redirects,ResourceRequest::RedirectStatus redirect_status,ContentSecurityPolicy::CheckHeaderType check_header_type) const103 BaseFetchContext::CheckCSPForRequestInternal(
104 mojom::RequestContextType request_context,
105 const KURL& url,
106 const ResourceLoaderOptions& options,
107 ReportingDisposition reporting_disposition,
108 const KURL& url_before_redirects,
109 ResourceRequest::RedirectStatus redirect_status,
110 ContentSecurityPolicy::CheckHeaderType check_header_type) const {
111 if (ShouldBypassMainWorldCSP() ||
112 options.content_security_policy_option ==
113 network::mojom::CSPDisposition::DO_NOT_CHECK) {
114 return base::nullopt;
115 }
116
117 const ContentSecurityPolicy* csp = GetContentSecurityPolicy();
118 if (csp &&
119 !csp->AllowRequest(request_context, url,
120 options.content_security_policy_nonce,
121 options.integrity_metadata, options.parser_disposition,
122 url_before_redirects, redirect_status,
123 reporting_disposition, check_header_type)) {
124 return ResourceRequestBlockedReason::kCSP;
125 }
126 return base::nullopt;
127 }
128
129 base::Optional<ResourceRequestBlockedReason>
CanRequestInternal(ResourceType type,const ResourceRequest & resource_request,const KURL & url,const ResourceLoaderOptions & options,ReportingDisposition reporting_disposition,const Vector<KURL> & redirect_chain) const130 BaseFetchContext::CanRequestInternal(ResourceType type,
131 const ResourceRequest& resource_request,
132 const KURL& url,
133 const ResourceLoaderOptions& options,
134 ReportingDisposition reporting_disposition,
135 const Vector<KURL>& redirect_chain) const {
136 if (GetResourceFetcherProperties().IsDetached()) {
137 if (!resource_request.GetKeepalive() || redirect_chain.IsEmpty()) {
138 return ResourceRequestBlockedReason::kOther;
139 }
140 }
141
142 if (ShouldBlockRequestByInspector(resource_request.Url()))
143 return ResourceRequestBlockedReason::kInspector;
144
145 scoped_refptr<const SecurityOrigin> origin =
146 resource_request.RequestorOrigin();
147
148 const auto request_mode = resource_request.GetMode();
149 // On navigation cases, Context().GetSecurityOrigin() may return nullptr, so
150 // the request's origin may be nullptr.
151 // TODO(yhirano): Figure out if it's actually fine.
152 DCHECK(request_mode == network::mojom::RequestMode::kNavigate || origin);
153 if (request_mode != network::mojom::RequestMode::kNavigate &&
154 !resource_request.CanDisplay(url)) {
155 if (reporting_disposition == ReportingDisposition::kReport) {
156 AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
157 mojom::ConsoleMessageSource::kJavaScript,
158 mojom::ConsoleMessageLevel::kError,
159 "Not allowed to load local resource: " + url.GetString()));
160 }
161 RESOURCE_LOADING_DVLOG(1) << "ResourceFetcher::requestResource URL was not "
162 "allowed by SecurityOrigin::CanDisplay";
163 return ResourceRequestBlockedReason::kOther;
164 }
165
166 if (request_mode == network::mojom::RequestMode::kSameOrigin &&
167 cors::CalculateCorsFlag(url, origin.get(),
168 resource_request.IsolatedWorldOrigin().get(),
169 request_mode)) {
170 PrintAccessDeniedMessage(url);
171 return ResourceRequestBlockedReason::kOrigin;
172 }
173
174 // User Agent CSS stylesheets should only support loading images and should be
175 // restricted to data urls.
176 if (options.initiator_info.name == fetch_initiator_type_names::kUacss) {
177 if (type == ResourceType::kImage && url.ProtocolIsData()) {
178 return base::nullopt;
179 }
180 return ResourceRequestBlockedReason::kOther;
181 }
182
183 mojom::RequestContextType request_context =
184 resource_request.GetRequestContext();
185
186 const KURL& url_before_redirects =
187 redirect_chain.IsEmpty() ? url : redirect_chain.front();
188 const ResourceRequestHead::RedirectStatus redirect_status =
189 redirect_chain.IsEmpty()
190 ? ResourceRequestHead::RedirectStatus::kNoRedirect
191 : ResourceRequestHead::RedirectStatus::kFollowedRedirect;
192 // We check the 'report-only' headers before upgrading the request (in
193 // populateResourceRequest). We check the enforced headers here to ensure we
194 // block things we ought to block.
195 if (CheckCSPForRequestInternal(
196 request_context, url, options, reporting_disposition, url_before_redirects, redirect_status,
197 ContentSecurityPolicy::CheckHeaderType::kCheckEnforce) ==
198 ResourceRequestBlockedReason::kCSP) {
199 return ResourceRequestBlockedReason::kCSP;
200 }
201
202 if (type == ResourceType::kScript || type == ResourceType::kImportResource) {
203 if (!AllowScriptFromSource(url)) {
204 // TODO(estark): Use a different ResourceRequestBlockedReason here, since
205 // this check has nothing to do with CSP. https://crbug.com/600795
206 return ResourceRequestBlockedReason::kCSP;
207 }
208 }
209
210 // SVG Images have unique security rules that prevent all subresource requests
211 // except for data urls.
212 if (IsSVGImageChromeClient() && !url.ProtocolIsData())
213 return ResourceRequestBlockedReason::kOrigin;
214
215 // Measure the number of legacy URL schemes ('ftp://') and the number of
216 // embedded-credential ('http://user:password@...') resources embedded as
217 // subresources.
218 const FetchClientSettingsObject& fetch_client_settings_object =
219 GetResourceFetcherProperties().GetFetchClientSettingsObject();
220 const SecurityOrigin* embedding_origin =
221 fetch_client_settings_object.GetSecurityOrigin();
222 DCHECK(embedding_origin);
223 if (SchemeRegistry::ShouldTreatURLSchemeAsLegacy(url.Protocol()) &&
224 !SchemeRegistry::ShouldTreatURLSchemeAsLegacy(
225 embedding_origin->Protocol())) {
226 CountDeprecation(WebFeature::kLegacyProtocolEmbeddedAsSubresource);
227
228 return ResourceRequestBlockedReason::kOrigin;
229 }
230
231 if (ShouldBlockFetchAsCredentialedSubresource(resource_request, url))
232 return ResourceRequestBlockedReason::kOrigin;
233
234 // Check for mixed content. We do this second-to-last so that when folks block
235 // mixed content via CSP, they don't get a mixed content warning, but a CSP
236 // warning instead.
237 if (ShouldBlockFetchByMixedContentCheck(request_context, redirect_chain, url,
238 reporting_disposition)) {
239 return ResourceRequestBlockedReason::kMixedContent;
240 }
241
242 if (url.PotentiallyDanglingMarkup() && url.ProtocolIsInHTTPFamily()) {
243 CountDeprecation(WebFeature::kCanRequestURLHTTPContainingNewline);
244 return ResourceRequestBlockedReason::kOther;
245 }
246
247 // Loading of a subresource may be blocked by previews resource loading hints.
248 if (GetPreviewsResourceLoadingHints() &&
249 !GetPreviewsResourceLoadingHints()->AllowLoad(
250 type, url, resource_request.Priority())) {
251 return ResourceRequestBlockedReason::kOther;
252 }
253
254 if (SendConversionRequestInsteadOfRedirecting(url, redirect_chain,
255 reporting_disposition)) {
256 return ResourceRequestBlockedReason::kOther;
257 }
258
259 // Let the client have the final say into whether or not the load should
260 // proceed.
261 if (GetSubresourceFilter() && type != ResourceType::kImportResource) {
262 if (!GetSubresourceFilter()->AllowLoad(url, request_context,
263 reporting_disposition)) {
264 return ResourceRequestBlockedReason::kSubresourceFilter;
265 }
266 }
267
268 return base::nullopt;
269 }
270
Trace(Visitor * visitor)271 void BaseFetchContext::Trace(Visitor* visitor) {
272 visitor->Trace(fetcher_properties_);
273 FetchContext::Trace(visitor);
274 }
275
276 } // namespace blink
277