Use NetworkLoadChecker for navigation loads
[WebKit-https.git] / Source / WebKit / NetworkProcess / NetworkLoadChecker.cpp
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkLoadChecker.h"
28
29 #include "Logging.h"
30 #include "NetworkCORSPreflightChecker.h"
31 #include "NetworkProcess.h"
32 #include "WebCompiledContentRuleList.h"
33 #include "WebUserContentController.h"
34 #include <WebCore/ContentSecurityPolicy.h>
35 #include <WebCore/CrossOriginAccessControl.h>
36 #include <WebCore/CrossOriginPreflightResultCache.h>
37 #include <WebCore/HTTPParsers.h>
38 #include <WebCore/SchemeRegistry.h>
39
40 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - NetworkLoadChecker::" fmt, this, ##__VA_ARGS__)
41
42 namespace WebKit {
43
44 using namespace WebCore;
45
46 static inline bool isSameOrigin(const URL& url, const SecurityOrigin* origin)
47 {
48     return url.protocolIsData() || url.protocolIsBlob() || !origin || origin->canRequest(url);
49 }
50
51 NetworkLoadChecker::NetworkLoadChecker(FetchOptions&& options, PAL::SessionID sessionID, HTTPHeaderMap&& originalRequestHeaders, URL&& url, RefPtr<SecurityOrigin>&& sourceOrigin, PreflightPolicy preflightPolicy)
52     : m_options(WTFMove(options))
53     , m_sessionID(sessionID)
54     , m_originalRequestHeaders(WTFMove(originalRequestHeaders))
55     , m_url(WTFMove(url))
56     , m_origin(WTFMove(sourceOrigin))
57     , m_preflightPolicy(preflightPolicy)
58 {
59     m_isSameOriginRequest = isSameOrigin(m_url, m_origin.get());
60     switch (options.credentials) {
61     case FetchOptions::Credentials::Include:
62         m_storedCredentialsPolicy = StoredCredentialsPolicy::Use;
63         break;
64     case FetchOptions::Credentials::SameOrigin:
65         m_storedCredentialsPolicy = m_isSameOriginRequest ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse;
66         break;
67     case FetchOptions::Credentials::Omit:
68         m_storedCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
69         break;
70     }
71 }
72
73 NetworkLoadChecker::~NetworkLoadChecker() = default;
74
75 void NetworkLoadChecker::check(ResourceRequest&& request, ValidationHandler&& handler)
76 {
77     ASSERT(!isChecking());
78
79     m_firstRequestHeaders = request.httpHeaderFields();
80     // FIXME: We should not get this information from the request but directly from some NetworkProcess setting.
81     m_dntHeaderValue = m_firstRequestHeaders.get(HTTPHeaderName::DNT);
82     if (m_dntHeaderValue.isNull() && m_sessionID.isEphemeral()) {
83         m_dntHeaderValue = "1";
84         request.setHTTPHeaderField(HTTPHeaderName::DNT, m_dntHeaderValue);
85     }
86     checkRequest(WTFMove(request), WTFMove(handler));
87 }
88
89 void NetworkLoadChecker::prepareRedirectedRequest(ResourceRequest& request)
90 {
91     if (!m_dntHeaderValue.isNull())
92         request.setHTTPHeaderField(HTTPHeaderName::DNT, m_dntHeaderValue);
93 }
94
95 void NetworkLoadChecker::checkRedirection(ResourceResponse& redirectResponse, ResourceRequest&& request, ValidationHandler&& handler)
96 {
97     ASSERT(!isChecking());
98
99     auto error = validateResponse(redirectResponse);
100     if (!error.isNull()) {
101         auto errorMessage = makeString("Cross-origin redirection to ", request.url().string(), " denied by Cross-Origin Resource Sharing policy: ", error.localizedDescription());
102         handler(makeUnexpected(ResourceError { String { }, 0, request.url(), WTFMove(errorMessage), ResourceError::Type::AccessControl }));
103         return;
104     }
105
106     if (m_options.redirect != FetchOptions::Redirect::Follow) {
107         handler(accessControlErrorForValidationHandler(ASCIILiteral("Redirections are not allowed")));
108         return;
109     }
110
111     // FIXME: We should check that redirections are only HTTP(s) as per fetch spec.
112     // See https://github.com/whatwg/fetch/issues/393
113
114     if (++m_redirectCount > 20) {
115         handler(accessControlErrorForValidationHandler(ASCIILiteral("Load cannot follow more than 20 redirections")));
116         return;
117     }
118
119     m_previousURL = WTFMove(m_url);
120     m_url = request.url();
121
122     checkRequest(WTFMove(request), WTFMove(handler));
123 }
124
125 ResourceError NetworkLoadChecker::validateResponse(ResourceResponse& response)
126 {
127     if (m_redirectCount)
128         response.setRedirected(true);
129
130     if (response.type() == ResourceResponse::Type::Opaqueredirect) {
131         response.setTainting(ResourceResponse::Tainting::Opaqueredirect);
132         return { };
133     }
134
135     if (m_options.mode == FetchOptions::Mode::Navigate || m_isSameOriginRequest) {
136         response.setTainting(ResourceResponse::Tainting::Basic);
137         return { };
138     }
139
140     if (m_options.mode == FetchOptions::Mode::NoCors) {
141         response.setTainting(ResourceResponse::Tainting::Opaque);
142         return { };
143     }
144
145     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
146
147     String errorMessage;
148     if (!passesAccessControlCheck(response, m_storedCredentialsPolicy, *m_origin, errorMessage))
149         return ResourceError { String { }, 0, m_url, WTFMove(errorMessage), ResourceError::Type::AccessControl };
150
151     response.setTainting(ResourceResponse::Tainting::Cors);
152     return { };
153 }
154
155 auto NetworkLoadChecker::accessControlErrorForValidationHandler(String&& message) -> RequestOrError
156 {
157     return makeUnexpected(ResourceError { String { }, 0, m_url, WTFMove(message), ResourceError::Type::AccessControl });
158 }
159
160 void NetworkLoadChecker::checkRequest(ResourceRequest&& request, ValidationHandler&& handler)
161 {
162 #if ENABLE(CONTENT_EXTENSIONS)
163     processContentExtensionRulesForLoad(WTFMove(request), [this, handler = WTFMove(handler)](auto&& request, auto status) mutable {
164         if (status.blockedLoad) {
165             handler(this->accessControlErrorForValidationHandler(ASCIILiteral("Blocked by content extension")));
166             return;
167         }
168         this->continueCheckingRequest(WTFMove(request), WTFMove(handler));
169     });
170 #else
171     continueCheckingRequest(WTFMove(request), WTFMove(handler));
172 #endif
173 }
174
175 void NetworkLoadChecker::continueCheckingRequest(ResourceRequest&& request, ValidationHandler&& handler)
176 {
177     if (auto* contentSecurityPolicy = this->contentSecurityPolicy()) {
178         if (isRedirected()) {
179             URL url = request.url();
180             auto type = m_options.mode == FetchOptions::Mode::Navigate ? ContentSecurityPolicy::InsecureRequestType::Navigation : ContentSecurityPolicy::InsecureRequestType::Load;
181             contentSecurityPolicy->upgradeInsecureRequestIfNeeded(url, type);
182             if (url != request.url())
183                 request.setURL(url);
184         }
185         if (m_options.destination == FetchOptions::Destination::EmptyString && !contentSecurityPolicy->allowConnectToSource(request.url(), isRedirected() ? ContentSecurityPolicy::RedirectResponseReceived::Yes : ContentSecurityPolicy::RedirectResponseReceived::No)) {
186             String message = !isRedirected() ? ASCIILiteral("Blocked by Content Security Policy") : makeString("Blocked ", request.url().string(), " by Content Security Policy");
187             handler(accessControlErrorForValidationHandler(WTFMove(message)));
188             return;
189         }
190     }
191
192     if (m_options.credentials == FetchOptions::Credentials::SameOrigin)
193         m_storedCredentialsPolicy = m_isSameOriginRequest && m_origin->canRequest(request.url()) ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse;
194
195     m_isSameOriginRequest = m_isSameOriginRequest && isSameOrigin(request.url(), m_origin.get());
196
197     if (doesNotNeedCORSCheck(request.url())) {
198         handler(WTFMove(request));
199         return;
200     }
201
202     if (m_options.mode == FetchOptions::Mode::SameOrigin) {
203         String message = makeString("Unsafe attempt to load URL ", request.url().stringCenterEllipsizedToLength(), " from origin ", m_origin->toString(), ". Domains, protocols and ports must match.\n");
204         handler(accessControlErrorForValidationHandler(WTFMove(message)));
205         return;
206     }
207
208     if (isRedirected()) {
209         RELEASE_LOG_IF_ALLOWED("checkRequest - Redirect requires CORS checks");
210         checkCORSRedirectedRequest(WTFMove(request), WTFMove(handler));
211         return;
212     }
213
214     checkCORSRequest(WTFMove(request), WTFMove(handler));
215 }
216
217 void NetworkLoadChecker::checkCORSRequest(ResourceRequest&& request, ValidationHandler&& handler)
218 {
219     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
220
221     // Except in case where preflight is needed, loading should be able to continue on its own.
222     switch (m_preflightPolicy) {
223     case PreflightPolicy::Force:
224         checkCORSRequestWithPreflight(WTFMove(request), WTFMove(handler));
225         break;
226     case PreflightPolicy::Consider:
227         if (!m_isSimpleRequest || !isSimpleCrossOriginAccessRequest(request.httpMethod(), m_originalRequestHeaders)) {
228             checkCORSRequestWithPreflight(WTFMove(request), WTFMove(handler));
229             return;
230         }
231         FALLTHROUGH;
232     case PreflightPolicy::Prevent:
233         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
234         handler(WTFMove(request));
235         break;
236     }
237 }
238
239 void NetworkLoadChecker::checkCORSRedirectedRequest(ResourceRequest&& request, ValidationHandler&& handler)
240 {
241     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
242     ASSERT(isRedirected());
243
244     // Force any subsequent request to use these checks.
245     m_isSameOriginRequest = false;
246
247     if (!m_origin->canRequest(m_previousURL) && !protocolHostAndPortAreEqual(m_previousURL, request.url())) {
248         // Use a unique origin for subsequent loads if needed.
249         // https://fetch.spec.whatwg.org/#concept-http-redirect-fetch (Step 10).
250         if (!m_origin || !m_origin->isUnique())
251             m_origin = SecurityOrigin::createUnique();
252     }
253
254     // FIXME: We should set the request referrer according the referrer policy.
255
256     // Let's fetch the request with the original headers (equivalent to request cloning specified by fetch algorithm).
257     if (!request.httpHeaderFields().contains(HTTPHeaderName::Authorization))
258         m_firstRequestHeaders.remove(HTTPHeaderName::Authorization);
259     request.setHTTPHeaderFields(m_firstRequestHeaders);
260
261     checkCORSRequest(WTFMove(request), WTFMove(handler));
262 }
263
264 void NetworkLoadChecker::checkCORSRequestWithPreflight(ResourceRequest&& request, ValidationHandler&& handler)
265 {
266     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
267
268     m_isSimpleRequest = false;
269     // FIXME: We should probably partition preflight result cache by session ID.
270     if (CrossOriginPreflightResultCache::singleton().canSkipPreflight(m_origin->toString(), request.url(), m_storedCredentialsPolicy, request.httpMethod(), m_originalRequestHeaders)) {
271         RELEASE_LOG_IF_ALLOWED("checkCORSRequestWithPreflight - preflight can be skipped thanks to cached result");
272         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
273         handler(WTFMove(request));
274         return;
275     }
276
277     auto requestForPreflight = request;
278     // We need to set header fields to m_originalRequestHeaders to correctly compute Access-Control-Request-Headers header value.
279     requestForPreflight.setHTTPHeaderFields(m_originalRequestHeaders);
280     NetworkCORSPreflightChecker::Parameters parameters = {
281         WTFMove(requestForPreflight),
282         *m_origin,
283         request.httpReferrer(),
284         m_sessionID,
285         m_storedCredentialsPolicy
286     };
287     m_corsPreflightChecker = std::make_unique<NetworkCORSPreflightChecker>(WTFMove(parameters), [this, request = WTFMove(request), handler = WTFMove(handler), isRedirected = isRedirected()](auto&& error) mutable {
288         RELEASE_LOG_IF_ALLOWED("checkCORSRequestWithPreflight - makeCrossOriginAccessRequestWithPreflight preflight complete, success: %d forRedirect? %d", error.isNull(), isRedirected);
289
290         if (!error.isNull()) {
291             handler(makeUnexpected(WTFMove(error)));
292             return;
293         }
294
295         auto corsPreflightChecker = WTFMove(m_corsPreflightChecker);
296         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
297         handler(WTFMove(request));
298     });
299     m_corsPreflightChecker->startPreflight();
300 }
301
302 bool NetworkLoadChecker::doesNotNeedCORSCheck(const URL& url) const
303 {
304     if (m_options.mode == FetchOptions::Mode::NoCors || m_options.mode == FetchOptions::Mode::Navigate)
305         return true;
306
307     if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol().toStringWithoutCopying()))
308         return true;
309
310     return m_isSameOriginRequest;
311 }
312
313 ContentSecurityPolicy* NetworkLoadChecker::contentSecurityPolicy() const
314 {
315     if (!m_contentSecurityPolicy && m_cspResponseHeaders) {
316         m_contentSecurityPolicy = std::make_unique<ContentSecurityPolicy>(*m_origin);
317         m_contentSecurityPolicy->didReceiveHeaders(*m_cspResponseHeaders, ContentSecurityPolicy::ReportParsingErrors::No);
318     }
319     return m_contentSecurityPolicy.get();
320 }
321
322 #if ENABLE(CONTENT_EXTENSIONS)
323 void NetworkLoadChecker::processContentExtensionRulesForLoad(ResourceRequest&& request, CompletionHandler<void(ResourceRequest&&, const ContentExtensions::BlockedStatus&)>&& callback)
324 {
325     // FIXME: Enable content blockers for navigation loads.
326     if (!m_userContentControllerIdentifier || m_options.mode == FetchOptions::Mode::Navigate) {
327         ContentExtensions::BlockedStatus status;
328         callback(WTFMove(request), status);
329         return;
330     }
331     NetworkProcess::singleton().networkContentRuleListManager().contentExtensionsBackend(*m_userContentControllerIdentifier, [protectedThis = makeRef(*this), this, request = WTFMove(request), callback = WTFMove(callback)](auto& backend) mutable {
332         auto status = backend.processContentExtensionRulesForPingLoad(request.url(), m_mainDocumentURL);
333         applyBlockedStatusToRequest(status, nullptr, request);
334         callback(WTFMove(request), status);
335     });
336 }
337 #endif // ENABLE(CONTENT_EXTENSIONS)
338
339 } // namespace WebKit