Web Inspector: Include Beacon and Ping requests in Network tab
[WebKit-https.git] / Source / WebKit / NetworkProcess / PingLoad.cpp
1 /*
2  * Copyright (C) 2016-2017 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 "PingLoad.h"
28
29 #if USE(NETWORK_SESSION)
30
31 #include "AuthenticationManager.h"
32 #include "Logging.h"
33 #include "NetworkCORSPreflightChecker.h"
34 #include "SessionTracker.h"
35 #include "WebCompiledContentRuleList.h"
36 #include "WebErrors.h"
37 #include <WebCore/ContentSecurityPolicy.h>
38 #include <WebCore/CrossOriginAccessControl.h>
39 #include <WebCore/CrossOriginPreflightResultCache.h>
40
41 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_parameters.sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - PingLoad::" fmt, this, ##__VA_ARGS__)
42
43 namespace WebKit {
44
45 using namespace WebCore;
46
47 PingLoad::PingLoad(NetworkResourceLoadParameters&& parameters, HTTPHeaderMap&& originalRequestHeaders, WTF::CompletionHandler<void(const ResourceError&, const ResourceResponse&)>&& completionHandler)
48     : m_parameters(WTFMove(parameters))
49     , m_originalRequestHeaders(WTFMove(originalRequestHeaders))
50     , m_completionHandler(WTFMove(completionHandler))
51     , m_timeoutTimer(*this, &PingLoad::timeoutTimerFired)
52     , m_isSameOriginRequest(securityOrigin().canRequest(m_parameters.request.url()))
53 {
54     ASSERT(m_parameters.sourceOrigin);
55
56     // If the server never responds, this object will hang around forever.
57     // Set a very generous timeout, just in case.
58     m_timeoutTimer.startOneShot(60000_s);
59
60     if (m_isSameOriginRequest || m_parameters.mode == FetchOptions::Mode::NoCors) {
61         loadRequest(ResourceRequest { m_parameters.request });
62         return;
63     }
64
65     makeCrossOriginAccessRequest(ResourceRequest { m_parameters.request });
66 }
67
68 PingLoad::~PingLoad()
69 {
70     if (m_redirectHandler)
71         m_redirectHandler({ });
72
73     if (m_task) {
74         ASSERT(m_task->client() == this);
75         m_task->clearClient();
76         m_task->cancel();
77     }
78 }
79
80 void PingLoad::didFinish(const ResourceError& error, const ResourceResponse& response)
81 {
82     m_completionHandler(error, response);
83     delete this;
84 }
85
86 void PingLoad::loadRequest(ResourceRequest&& request)
87 {
88     RELEASE_LOG_IF_ALLOWED("startNetworkLoad");
89     if (auto* networkSession = SessionTracker::networkSession(m_parameters.sessionID)) {
90         auto loadParameters = m_parameters;
91         loadParameters.request = WTFMove(request);
92         m_task = NetworkDataTask::create(*networkSession, *this, WTFMove(loadParameters));
93         m_task->resume();
94     } else
95         ASSERT_NOT_REACHED();
96 }
97
98 SecurityOrigin& PingLoad::securityOrigin() const
99 {
100     return m_origin ? *m_origin : *m_parameters.sourceOrigin;
101 }
102
103 void PingLoad::willPerformHTTPRedirection(ResourceResponse&& redirectResponse, ResourceRequest&& request, RedirectCompletionHandler&& completionHandler)
104 {
105     RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - shouldFollowRedirects? %d", m_parameters.shouldFollowRedirects);
106     if (!m_parameters.shouldFollowRedirects) {
107         completionHandler({ });
108         didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Not allowed to follow redirects"), ResourceError::Type::AccessControl });
109         return;
110     }
111
112 #if ENABLE(CONTENT_EXTENSIONS)
113     if (processContentExtensionRulesForLoad(request).blockedLoad) {
114         RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - Redirect was blocked by content extensions");
115         m_lastRedirectionRequest = request;
116         completionHandler({ });
117         didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Blocked by content extension"), ResourceError::Type::AccessControl });
118         return;
119     }
120 #endif
121
122     m_lastRedirectionRequest = request;
123
124     if (auto* contentSecurityPolicy = this->contentSecurityPolicy()) {
125         if (!contentSecurityPolicy->allowConnectToSource(request.url(), redirectResponse.isNull() ? ContentSecurityPolicy::RedirectResponseReceived::No : ContentSecurityPolicy::RedirectResponseReceived::Yes)) {
126             RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - Redirect was blocked by CSP");
127             completionHandler({ });
128             didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Blocked by Content Security Policy"), ResourceError::Type::AccessControl });
129             return;
130         }
131     }
132
133     // FIXME: We should ensure the number of redirects does not exceed 20.
134
135     if (isAllowedRedirect(request.url())) {
136         completionHandler(WTFMove(request));
137         return;
138     }
139     RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - Redirect requires a CORS preflight");
140
141     // Force any subsequent request to use these checks.
142     m_isSameOriginRequest = false;
143
144     // Use a unique origin for subsequent loads if needed.
145     // https://fetch.spec.whatwg.org/#concept-http-redirect-fetch (Step 10).
146     ASSERT(m_parameters.mode == FetchOptions::Mode::Cors);
147     if (!securityOrigin().canRequest(redirectResponse.url()) && !protocolHostAndPortAreEqual(redirectResponse.url(), request.url())) {
148         if (!m_origin || !m_origin->isUnique())
149             m_origin = SecurityOrigin::createUnique();
150     }
151
152     // Except in case where preflight is needed, loading should be able to continue on its own.
153     if (m_isSimpleRequest && isSimpleCrossOriginAccessRequest(request.httpMethod(), m_originalRequestHeaders)) {
154         completionHandler(WTFMove(request));
155         return;
156     }
157
158     m_parameters.storedCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
159     m_redirectHandler = WTFMove(completionHandler);
160
161     // Let's fetch the request with the original headers (equivalent to request cloning specified by fetch algorithm).
162     request.setHTTPHeaderFields(m_parameters.request.httpHeaderFields());
163
164     makeCrossOriginAccessRequest(WTFMove(request));
165 }
166
167 void PingLoad::didReceiveChallenge(const AuthenticationChallenge&, ChallengeCompletionHandler&& completionHandler)
168 {
169     RELEASE_LOG_IF_ALLOWED("didReceiveChallenge");
170     completionHandler(AuthenticationChallengeDisposition::Cancel, { });
171     didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Failed HTTP authentication"), ResourceError::Type::AccessControl });
172 }
173
174 void PingLoad::didReceiveResponseNetworkSession(ResourceResponse&& response, ResponseCompletionHandler&& completionHandler)
175 {
176     RELEASE_LOG_IF_ALLOWED("didReceiveResponseNetworkSession - httpStatusCode: %d", response.httpStatusCode());
177     completionHandler(PolicyAction::Ignore);
178     didFinish({ }, response);
179 }
180
181 void PingLoad::didReceiveData(Ref<SharedBuffer>&&)
182 {
183     RELEASE_LOG_IF_ALLOWED("didReceiveData");
184     ASSERT_NOT_REACHED();
185 }
186
187 void PingLoad::didCompleteWithError(const ResourceError& error, const NetworkLoadMetrics&)
188 {
189     if (error.isNull())
190         RELEASE_LOG_IF_ALLOWED("didComplete");
191     else
192         RELEASE_LOG_IF_ALLOWED("didCompleteWithError, error_code: %d", error.errorCode());
193
194     didFinish(error);
195 }
196
197 void PingLoad::didSendData(uint64_t totalBytesSent, uint64_t totalBytesExpectedToSend)
198 {
199 }
200
201 void PingLoad::wasBlocked()
202 {
203     RELEASE_LOG_IF_ALLOWED("wasBlocked");
204     didFinish(blockedError(currentRequest()));
205 }
206
207 void PingLoad::cannotShowURL()
208 {
209     RELEASE_LOG_IF_ALLOWED("cannotShowURL");
210     didFinish(cannotShowURLError(currentRequest()));
211 }
212
213 void PingLoad::timeoutTimerFired()
214 {
215     RELEASE_LOG_IF_ALLOWED("timeoutTimerFired");
216     didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Load timed out"), ResourceError::Type::Timeout });
217 }
218
219 const ResourceRequest& PingLoad::currentRequest() const
220 {
221     if (m_lastRedirectionRequest)
222         return *m_lastRedirectionRequest;
223
224     return m_parameters.request;
225 }
226
227 bool PingLoad::isAllowedRedirect(const URL& url) const
228 {
229     if (m_parameters.mode == FetchOptions::Mode::NoCors)
230         return true;
231
232     return m_isSameOriginRequest && securityOrigin().canRequest(url);
233 }
234
235 ContentSecurityPolicy* PingLoad::contentSecurityPolicy() const
236 {
237     if (!m_contentSecurityPolicy && m_parameters.cspResponseHeaders) {
238         m_contentSecurityPolicy = std::make_unique<ContentSecurityPolicy>(*m_parameters.sourceOrigin);
239         m_contentSecurityPolicy->didReceiveHeaders(*m_parameters.cspResponseHeaders, ContentSecurityPolicy::ReportParsingErrors::No);
240     }
241     return m_contentSecurityPolicy.get();
242 }
243
244 void PingLoad::makeCrossOriginAccessRequest(ResourceRequest&& request)
245 {
246     ASSERT(m_parameters.mode == FetchOptions::Mode::Cors);
247     RELEASE_LOG_IF_ALLOWED("makeCrossOriginAccessRequest");
248
249     if (isSimpleCrossOriginAccessRequest(request.httpMethod(), m_originalRequestHeaders)) {
250         makeSimpleCrossOriginAccessRequest(WTFMove(request));
251         return;
252     }
253
254     m_isSimpleRequest = false;
255     if (CrossOriginPreflightResultCache::singleton().canSkipPreflight(securityOrigin().toString(), request.url(), m_parameters.storedCredentialsPolicy, request.httpMethod(), m_originalRequestHeaders)) {
256         RELEASE_LOG_IF_ALLOWED("makeCrossOriginAccessRequest - preflight can be skipped thanks to cached result");
257         preflightSuccess(WTFMove(request));
258     } else
259         makeCrossOriginAccessRequestWithPreflight(WTFMove(request));
260 }
261
262 void PingLoad::makeSimpleCrossOriginAccessRequest(ResourceRequest&& request)
263 {
264     ASSERT(isSimpleCrossOriginAccessRequest(request.httpMethod(), m_originalRequestHeaders));
265     RELEASE_LOG_IF_ALLOWED("makeSimpleCrossOriginAccessRequest");
266
267     if (!request.url().protocolIsInHTTPFamily()) {
268         RELEASE_LOG_IF_ALLOWED("makeSimpleCrossOriginAccessRequest: Cross origin requests are only supported for HTTP.");
269         return;
270     }
271
272     updateRequestForAccessControl(request, securityOrigin(), m_parameters.storedCredentialsPolicy);
273     loadRequest(WTFMove(request));
274 }
275
276 void PingLoad::makeCrossOriginAccessRequestWithPreflight(ResourceRequest&& request)
277 {
278     RELEASE_LOG_IF_ALLOWED("makeCrossOriginAccessRequestWithPreflight");
279     ASSERT(!m_corsPreflightChecker);
280
281     NetworkCORSPreflightChecker::Parameters parameters = {
282         WTFMove(request),
283         securityOrigin(),
284         m_parameters.sessionID,
285         m_parameters.storedCredentialsPolicy
286     };
287     m_corsPreflightChecker = std::make_unique<NetworkCORSPreflightChecker>(WTFMove(parameters), [this](NetworkCORSPreflightChecker::Result result) {
288         RELEASE_LOG_IF_ALLOWED("makeCrossOriginAccessRequestWithPreflight preflight complete, success: %d forRedirect? %d", result == NetworkCORSPreflightChecker::Result::Success, !!m_redirectHandler);
289         auto corsPreflightChecker = WTFMove(m_corsPreflightChecker);
290         if (result == NetworkCORSPreflightChecker::Result::Success)
291             preflightSuccess(ResourceRequest { corsPreflightChecker->originalRequest() });
292         else
293             didFinish(ResourceError { String(), 0, corsPreflightChecker->originalRequest().url(), ASCIILiteral("CORS preflight failed"), ResourceError::Type::AccessControl });
294     });
295     m_corsPreflightChecker->startPreflight();
296 }
297
298 void PingLoad::preflightSuccess(ResourceRequest&& request)
299 {
300     RELEASE_LOG_IF_ALLOWED("preflightSuccess");
301
302     ResourceRequest actualRequest = WTFMove(request);
303     updateRequestForAccessControl(actualRequest, securityOrigin(), m_parameters.storedCredentialsPolicy);
304
305     if (auto redirectHandler = std::exchange(m_redirectHandler, nullptr))
306         redirectHandler(WTFMove(actualRequest));
307     else
308         loadRequest(WTFMove(actualRequest));
309 }
310
311 #if ENABLE(CONTENT_EXTENSIONS)
312
313 ContentExtensions::ContentExtensionsBackend& PingLoad::contentExtensionsBackend()
314 {
315     if (!m_contentExtensionsBackend) {
316         m_contentExtensionsBackend = std::make_unique<ContentExtensions::ContentExtensionsBackend>();
317         for (auto& pair : m_parameters.contentRuleLists)
318             m_contentExtensionsBackend->addContentExtension(pair.first, WebCompiledContentRuleList::create(WTFMove(pair.second)));
319     }
320     return *m_contentExtensionsBackend;
321 }
322
323 ContentExtensions::BlockedStatus PingLoad::processContentExtensionRulesForLoad(ResourceRequest& request)
324 {
325     auto status = contentExtensionsBackend().processContentExtensionRulesForPingLoad(request.url(), m_parameters.mainDocumentURL);
326     applyBlockedStatusToRequest(status, nullptr, request);
327     return status;
328 }
329
330 #endif // ENABLE(CONTENT_EXTENSIONS)
331
332 } // namespace WebKit
333
334 #endif // USE(NETWORK_SESSION)