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