ec2461d0d239082a1cbc931e5c126b43fa0d7eb6
[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 NetworkLoadChecker::NetworkLoadChecker(FetchOptions&& options, PAL::SessionID sessionID, HTTPHeaderMap&& originalRequestHeaders, URL&& url, RefPtr<SecurityOrigin>&& sourceOrigin, PreflightPolicy preflightPolicy)
47     : m_options(WTFMove(options))
48     , m_sessionID(sessionID)
49     , m_originalRequestHeaders(WTFMove(originalRequestHeaders))
50     , m_url(WTFMove(url))
51     , m_origin(WTFMove(sourceOrigin))
52     , m_preflightPolicy(preflightPolicy)
53 {
54     if (m_options.mode == FetchOptions::Mode::Cors || m_options.mode == FetchOptions::Mode::SameOrigin)
55         m_isSameOriginRequest = m_url.protocolIsData() || m_url.protocolIsBlob() || m_origin->canRequest(m_url);
56     switch (options.credentials) {
57     case FetchOptions::Credentials::Include:
58         m_storedCredentialsPolicy = StoredCredentialsPolicy::Use;
59         break;
60     case FetchOptions::Credentials::SameOrigin:
61         m_storedCredentialsPolicy = m_isSameOriginRequest ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse;
62         break;
63     case FetchOptions::Credentials::Omit:
64         m_storedCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
65         break;
66     }
67 }
68
69 NetworkLoadChecker::~NetworkLoadChecker() = default;
70
71 void NetworkLoadChecker::check(ResourceRequest&& request, ValidationHandler&& handler)
72 {
73     ASSERT(!isChecking());
74
75     m_firstRequestHeaders = request.httpHeaderFields();
76     // FIXME: We should not get this information from the request but directly from some NetworkProcess setting.
77     m_dntHeaderValue = m_firstRequestHeaders.get(HTTPHeaderName::DNT);
78     if (m_dntHeaderValue.isNull() && m_sessionID.isEphemeral()) {
79         m_dntHeaderValue = "1";
80         request.setHTTPHeaderField(HTTPHeaderName::DNT, m_dntHeaderValue);
81     }
82     checkRequest(WTFMove(request), WTFMove(handler));
83 }
84
85 void NetworkLoadChecker::prepareRedirectedRequest(ResourceRequest& request)
86 {
87     if (!m_dntHeaderValue.isNull())
88         request.setHTTPHeaderField(HTTPHeaderName::DNT, m_dntHeaderValue);
89 }
90
91 void NetworkLoadChecker::checkRedirection(ResourceResponse& redirectResponse, ResourceRequest&& request, ValidationHandler&& handler)
92 {
93     ASSERT(!isChecking());
94
95     auto error = validateResponse(redirectResponse);
96     if (!error.isNull()) {
97         auto errorMessage = makeString("Cross-origin redirection to ", request.url().string(), " denied by Cross-Origin Resource Sharing policy: ", error.localizedDescription());
98         handler(makeUnexpected(ResourceError { String { }, 0, request.url(), WTFMove(errorMessage), ResourceError::Type::AccessControl }));
99         return;
100     }
101
102     if (m_options.redirect != FetchOptions::Redirect::Follow) {
103         handler(accessControlErrorForValidationHandler(ASCIILiteral("Redirections are not allowed")));
104         return;
105     }
106
107     // FIXME: We should check that redirections are only HTTP(s) as per fetch spec.
108     // See https://github.com/whatwg/fetch/issues/393
109
110     if (++m_redirectCount > 20) {
111         handler(accessControlErrorForValidationHandler(ASCIILiteral("Load cannot follow more than 20 redirections")));
112         return;
113     }
114
115     m_previousURL = WTFMove(m_url);
116     m_url = request.url();
117
118     checkRequest(WTFMove(request), WTFMove(handler));
119 }
120
121 ResourceError NetworkLoadChecker::validateResponse(ResourceResponse& response)
122 {
123     if (m_redirectCount)
124         response.setRedirected(true);
125
126     if (response.type() == ResourceResponse::Type::Opaqueredirect) {
127         response.setTainting(ResourceResponse::Tainting::Opaqueredirect);
128         return { };
129     }
130
131     if (m_isSameOriginRequest) {
132         response.setTainting(ResourceResponse::Tainting::Basic);
133         return { };
134     }
135
136     if (m_options.mode == FetchOptions::Mode::NoCors) {
137         response.setTainting(ResourceResponse::Tainting::Opaque);
138         return { };
139     }
140
141     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
142
143     String errorMessage;
144     if (!passesAccessControlCheck(response, m_storedCredentialsPolicy, *m_origin, errorMessage))
145         return ResourceError { String { }, 0, m_url, WTFMove(errorMessage), ResourceError::Type::AccessControl };
146
147     response.setTainting(ResourceResponse::Tainting::Cors);
148     return { };
149 }
150
151 auto NetworkLoadChecker::accessControlErrorForValidationHandler(String&& message) -> RequestOrError
152 {
153     return makeUnexpected(ResourceError { String { }, 0, m_url, WTFMove(message), ResourceError::Type::AccessControl });
154 }
155
156 void NetworkLoadChecker::checkRequest(ResourceRequest&& request, ValidationHandler&& handler)
157 {
158 #if ENABLE(CONTENT_EXTENSIONS)
159     processContentExtensionRulesForLoad(WTFMove(request), [this, handler = WTFMove(handler)](auto&& request, auto status) mutable {
160         if (status.blockedLoad) {
161             handler(this->accessControlErrorForValidationHandler(ASCIILiteral("Blocked by content extension")));
162             return;
163         }
164         this->continueCheckingRequest(WTFMove(request), WTFMove(handler));
165     });
166 #else
167     continueCheckingRequest(WTFMove(request), WTFMove(handler));
168 #endif
169 }
170
171 void NetworkLoadChecker::continueCheckingRequest(ResourceRequest&& request, ValidationHandler&& handler)
172 {
173     if (auto* contentSecurityPolicy = this->contentSecurityPolicy()) {
174         if (isRedirected()) {
175             URL url = request.url();
176             auto type = m_options.mode == FetchOptions::Mode::Navigate ? ContentSecurityPolicy::InsecureRequestType::Navigation : ContentSecurityPolicy::InsecureRequestType::Load;
177             contentSecurityPolicy->upgradeInsecureRequestIfNeeded(url, type);
178             if (url != request.url())
179                 request.setURL(url);
180         }
181         if (m_options.destination == FetchOptions::Destination::EmptyString && !contentSecurityPolicy->allowConnectToSource(request.url(), isRedirected() ? ContentSecurityPolicy::RedirectResponseReceived::Yes : ContentSecurityPolicy::RedirectResponseReceived::No)) {
182             String message = !isRedirected() ? ASCIILiteral("Blocked by Content Security Policy") : makeString("Blocked ", request.url().string(), " by Content Security Policy");
183             handler(accessControlErrorForValidationHandler(WTFMove(message)));
184             return;
185         }
186     }
187
188     if (m_options.credentials == FetchOptions::Credentials::SameOrigin)
189         m_storedCredentialsPolicy = m_isSameOriginRequest && m_origin->canRequest(request.url()) ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse;
190
191     if (doesNotNeedCORSCheck(request.url())) {
192         handler(WTFMove(request));
193         return;
194     }
195
196     if (m_options.mode == FetchOptions::Mode::SameOrigin) {
197         String message = makeString("Unsafe attempt to load URL ", request.url().stringCenterEllipsizedToLength(), " from origin ", m_origin->toString(), ". Domains, protocols and ports must match.\n");
198         handler(accessControlErrorForValidationHandler(WTFMove(message)));
199         return;
200     }
201
202     if (isRedirected()) {
203         RELEASE_LOG_IF_ALLOWED("checkRequest - Redirect requires CORS checks");
204         checkCORSRedirectedRequest(WTFMove(request), WTFMove(handler));
205         return;
206     }
207
208     checkCORSRequest(WTFMove(request), WTFMove(handler));
209 }
210
211 void NetworkLoadChecker::checkCORSRequest(ResourceRequest&& request, ValidationHandler&& handler)
212 {
213     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
214
215     // Except in case where preflight is needed, loading should be able to continue on its own.
216     switch (m_preflightPolicy) {
217     case PreflightPolicy::Force:
218         checkCORSRequestWithPreflight(WTFMove(request), WTFMove(handler));
219         break;
220     case PreflightPolicy::Consider:
221         if (!m_isSimpleRequest || !isSimpleCrossOriginAccessRequest(request.httpMethod(), m_originalRequestHeaders)) {
222             checkCORSRequestWithPreflight(WTFMove(request), WTFMove(handler));
223             return;
224         }
225         FALLTHROUGH;
226     case PreflightPolicy::Prevent:
227         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
228         handler(WTFMove(request));
229         break;
230     }
231 }
232
233 void NetworkLoadChecker::checkCORSRedirectedRequest(ResourceRequest&& request, ValidationHandler&& handler)
234 {
235     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
236     ASSERT(isRedirected());
237
238     // Force any subsequent request to use these checks.
239     m_isSameOriginRequest = false;
240
241     if (!m_origin->canRequest(m_previousURL) && !protocolHostAndPortAreEqual(m_previousURL, request.url())) {
242         // Use a unique origin for subsequent loads if needed.
243         // https://fetch.spec.whatwg.org/#concept-http-redirect-fetch (Step 10).
244         if (!m_origin || !m_origin->isUnique())
245             m_origin = SecurityOrigin::createUnique();
246     }
247
248     // FIXME: We should set the request referrer according the referrer policy.
249
250     // Let's fetch the request with the original headers (equivalent to request cloning specified by fetch algorithm).
251     if (!request.httpHeaderFields().contains(HTTPHeaderName::Authorization))
252         m_firstRequestHeaders.remove(HTTPHeaderName::Authorization);
253     request.setHTTPHeaderFields(m_firstRequestHeaders);
254
255     checkCORSRequest(WTFMove(request), WTFMove(handler));
256 }
257
258 void NetworkLoadChecker::checkCORSRequestWithPreflight(ResourceRequest&& request, ValidationHandler&& handler)
259 {
260     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
261
262     m_isSimpleRequest = false;
263     // FIXME: We should probably partition preflight result cache by session ID.
264     if (CrossOriginPreflightResultCache::singleton().canSkipPreflight(m_origin->toString(), request.url(), m_storedCredentialsPolicy, request.httpMethod(), m_originalRequestHeaders)) {
265         RELEASE_LOG_IF_ALLOWED("checkCORSRequestWithPreflight - preflight can be skipped thanks to cached result");
266         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
267         handler(WTFMove(request));
268         return;
269     }
270
271     auto requestForPreflight = request;
272     // We need to set header fields to m_originalRequestHeaders to correctly compute Access-Control-Request-Headers header value.
273     requestForPreflight.setHTTPHeaderFields(m_originalRequestHeaders);
274     NetworkCORSPreflightChecker::Parameters parameters = {
275         WTFMove(requestForPreflight),
276         *m_origin,
277         request.httpReferrer(),
278         m_sessionID,
279         m_storedCredentialsPolicy
280     };
281     m_corsPreflightChecker = std::make_unique<NetworkCORSPreflightChecker>(WTFMove(parameters), [this, request = WTFMove(request), handler = WTFMove(handler), isRedirected = isRedirected()](auto&& error) mutable {
282         RELEASE_LOG_IF_ALLOWED("checkCORSRequestWithPreflight - makeCrossOriginAccessRequestWithPreflight preflight complete, success: %d forRedirect? %d", error.isNull(), isRedirected);
283
284         if (!error.isNull()) {
285             handler(makeUnexpected(WTFMove(error)));
286             return;
287         }
288
289         auto corsPreflightChecker = WTFMove(m_corsPreflightChecker);
290         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
291         handler(WTFMove(request));
292     });
293     m_corsPreflightChecker->startPreflight();
294 }
295
296 bool NetworkLoadChecker::doesNotNeedCORSCheck(const URL& url) const
297 {
298     if (m_options.mode == FetchOptions::Mode::NoCors || m_options.mode == FetchOptions::Mode::Navigate)
299         return true;
300
301     if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol().toStringWithoutCopying()))
302         return true;
303
304     return m_isSameOriginRequest && m_origin->canRequest(url);
305 }
306
307 ContentSecurityPolicy* NetworkLoadChecker::contentSecurityPolicy() const
308 {
309     if (!m_contentSecurityPolicy && m_cspResponseHeaders) {
310         m_contentSecurityPolicy = std::make_unique<ContentSecurityPolicy>(*m_origin);
311         m_contentSecurityPolicy->didReceiveHeaders(*m_cspResponseHeaders, ContentSecurityPolicy::ReportParsingErrors::No);
312     }
313     return m_contentSecurityPolicy.get();
314 }
315
316 #if ENABLE(CONTENT_EXTENSIONS)
317 void NetworkLoadChecker::processContentExtensionRulesForLoad(ResourceRequest&& request, CompletionHandler<void(ResourceRequest&&, const ContentExtensions::BlockedStatus&)>&& callback)
318 {
319     if (!m_userContentControllerIdentifier) {
320         ContentExtensions::BlockedStatus status;
321         callback(WTFMove(request), status);
322         return;
323     }
324     NetworkProcess::singleton().networkContentRuleListManager().contentExtensionsBackend(*m_userContentControllerIdentifier, [protectedThis = makeRef(*this), this, request = WTFMove(request), callback = WTFMove(callback)](auto& backend) mutable {
325         auto status = backend.processContentExtensionRulesForPingLoad(request.url(), m_mainDocumentURL);
326         applyBlockedStatusToRequest(status, nullptr, request);
327         callback(WTFMove(request), status);
328     });
329 }
330 #endif // ENABLE(CONTENT_EXTENSIONS)
331
332 } // namespace WebKit