1/*
2 * Copyright (C) 2012-2019 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 "NetworkResourceLoader.h"
28
29#include "DataReference.h"
30#include "FormDataReference.h"
31#include "Logging.h"
32#include "NetworkCache.h"
33#include "NetworkConnectionToWebProcess.h"
34#include "NetworkLoad.h"
35#include "NetworkLoadChecker.h"
36#include "NetworkProcess.h"
37#include "NetworkProcessConnectionMessages.h"
38#include "NetworkSession.h"
39#include "SharedBufferDataReference.h"
40#include "WebCoreArgumentCoders.h"
41#include "WebErrors.h"
42#include "WebPageMessages.h"
43#include "WebResourceLoaderMessages.h"
44#include "WebsiteDataStoreParameters.h"
45#include <WebCore/BlobDataFileReference.h>
46#include <WebCore/CertificateInfo.h>
47#include <WebCore/ContentSecurityPolicy.h>
48#include <WebCore/DiagnosticLoggingKeys.h>
49#include <WebCore/HTTPParsers.h>
50#include <WebCore/NetworkLoadMetrics.h>
51#include <WebCore/NetworkStorageSession.h>
52#include <WebCore/RegistrableDomain.h>
53#include <WebCore/SameSiteInfo.h>
54#include <WebCore/SecurityOrigin.h>
55#include <WebCore/SharedBuffer.h>
56#include <wtf/RunLoop.h>
57
58#if USE(QUICK_LOOK)
59#include <WebCore/PreviewConverter.h>
60#endif
61
62#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - NetworkResourceLoader::" fmt, this, ##__VA_ARGS__)
63#define RELEASE_LOG_ERROR_IF_ALLOWED(fmt, ...) RELEASE_LOG_ERROR_IF(isAlwaysOnLoggingAllowed(), Network, "%p - NetworkResourceLoader::" fmt, this, ##__VA_ARGS__)
64
65namespace WebKit {
66using namespace WebCore;
67
68struct NetworkResourceLoader::SynchronousLoadData {
69 WTF_MAKE_STRUCT_FAST_ALLOCATED;
70
71 SynchronousLoadData(Messages::NetworkConnectionToWebProcess::PerformSynchronousLoad::DelayedReply&& reply)
72 : delayedReply(WTFMove(reply))
73 {
74 ASSERT(delayedReply);
75 }
76 ResourceRequest currentRequest;
77 Messages::NetworkConnectionToWebProcess::PerformSynchronousLoad::DelayedReply delayedReply;
78 ResourceResponse response;
79 ResourceError error;
80};
81
82static void sendReplyToSynchronousRequest(NetworkResourceLoader::SynchronousLoadData& data, const SharedBuffer* buffer)
83{
84 ASSERT(data.delayedReply);
85 ASSERT(!data.response.isNull() || !data.error.isNull());
86
87 Vector<char> responseBuffer;
88 if (buffer && buffer->size())
89 responseBuffer.append(buffer->data(), buffer->size());
90
91 data.delayedReply(data.error, data.response, responseBuffer);
92 data.delayedReply = nullptr;
93}
94
95NetworkResourceLoader::NetworkResourceLoader(NetworkResourceLoadParameters&& parameters, NetworkConnectionToWebProcess& connection, Messages::NetworkConnectionToWebProcess::PerformSynchronousLoad::DelayedReply&& synchronousReply)
96 : m_parameters { WTFMove(parameters) }
97 , m_connection { connection }
98 , m_fileReferences(connection.resolveBlobReferences(m_parameters))
99 , m_isAllowedToAskUserForCredentials { m_parameters.clientCredentialPolicy == ClientCredentialPolicy::MayAskClientForCredentials }
100 , m_bufferingTimer { *this, &NetworkResourceLoader::bufferingTimerFired }
101 , m_cache { sessionID().isEphemeral() ? nullptr : connection.networkProcess().cache() }
102 , m_shouldCaptureExtraNetworkLoadMetrics(m_connection->captureExtraNetworkLoadMetricsEnabled())
103{
104 ASSERT(RunLoop::isMain());
105 // FIXME: This is necessary because of the existence of EmptyFrameLoaderClient in WebCore.
106 // Once bug 116233 is resolved, this ASSERT can just be "m_webPageID && m_webFrameID"
107 ASSERT((m_parameters.webPageID && m_parameters.webFrameID) || m_parameters.clientCredentialPolicy == ClientCredentialPolicy::CannotAskClientForCredentials);
108
109 if (synchronousReply || parameters.shouldRestrictHTTPResponseAccess || parameters.options.keepAlive) {
110 NetworkLoadChecker::LoadType requestLoadType = isMainFrameLoad() ? NetworkLoadChecker::LoadType::MainFrame : NetworkLoadChecker::LoadType::Other;
111 m_networkLoadChecker = std::make_unique<NetworkLoadChecker>(connection.networkProcess(), FetchOptions { m_parameters.options }, m_parameters.sessionID, m_parameters.webPageID, m_parameters.webFrameID, HTTPHeaderMap { m_parameters.originalRequestHeaders }, URL { m_parameters.request.url() }, m_parameters.sourceOrigin.copyRef(), m_parameters.preflightPolicy, originalRequest().httpReferrer(), m_parameters.isHTTPSUpgradeEnabled, shouldCaptureExtraNetworkLoadMetrics(), requestLoadType);
112 if (m_parameters.cspResponseHeaders)
113 m_networkLoadChecker->setCSPResponseHeaders(ContentSecurityPolicyResponseHeaders { m_parameters.cspResponseHeaders.value() });
114#if ENABLE(CONTENT_EXTENSIONS)
115 m_networkLoadChecker->setContentExtensionController(URL { m_parameters.mainDocumentURL }, m_parameters.userContentControllerIdentifier);
116#endif
117 }
118 if (synchronousReply)
119 m_synchronousLoadData = std::make_unique<SynchronousLoadData>(WTFMove(synchronousReply));
120}
121
122NetworkResourceLoader::~NetworkResourceLoader()
123{
124 ASSERT(RunLoop::isMain());
125 ASSERT(!m_networkLoad);
126 ASSERT(!isSynchronous() || !m_synchronousLoadData->delayedReply);
127 ASSERT(m_fileReferences.isEmpty());
128 if (m_responseCompletionHandler)
129 m_responseCompletionHandler(PolicyAction::Ignore);
130}
131
132bool NetworkResourceLoader::canUseCache(const ResourceRequest& request) const
133{
134 if (!m_cache)
135 return false;
136 ASSERT(!sessionID().isEphemeral());
137
138 if (!request.url().protocolIsInHTTPFamily())
139 return false;
140 if (originalRequest().cachePolicy() == WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache)
141 return false;
142
143 return true;
144}
145
146bool NetworkResourceLoader::canUseCachedRedirect(const ResourceRequest& request) const
147{
148 if (!canUseCache(request) || m_cacheEntryForMaxAgeCapValidation)
149 return false;
150 // Limit cached redirects to avoid cycles and other trouble.
151 // Networking layer follows over 30 redirects but caching that many seems unnecessary.
152 static const unsigned maximumCachedRedirectCount { 5 };
153 if (m_redirectCount > maximumCachedRedirectCount)
154 return false;
155
156 return true;
157}
158
159bool NetworkResourceLoader::isSynchronous() const
160{
161 return !!m_synchronousLoadData;
162}
163
164void NetworkResourceLoader::start()
165{
166 ASSERT(RunLoop::isMain());
167
168 m_networkActivityTracker = m_connection->startTrackingResourceLoad(m_parameters.webPageID, m_parameters.identifier, isMainResource(), sessionID());
169
170 ASSERT(!m_wasStarted);
171 m_wasStarted = true;
172
173 if (m_networkLoadChecker) {
174 m_networkLoadChecker->check(ResourceRequest { originalRequest() }, this, [this] (auto&& result) {
175 WTF::switchOn(result,
176 [this] (ResourceError& error) {
177 RELEASE_LOG_IF_ALLOWED("start: error checking (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d, parentPID = %d, error.domain = %{public}s, error.code = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, this->isMainResource(), this->isSynchronous(), m_parameters.parentPID, error.domain().utf8().data(), error.errorCode());
178 if (!error.isCancellation())
179 this->didFailLoading(error);
180 },
181 [this] (NetworkLoadChecker::RedirectionTriplet& triplet) {
182 this->m_isWaitingContinueWillSendRequestForCachedRedirect = true;
183 this->willSendRedirectedRequest(WTFMove(triplet.request), WTFMove(triplet.redirectRequest), WTFMove(triplet.redirectResponse));
184 RELEASE_LOG_IF_ALLOWED("start: synthetic redirect sent because request URL was modified (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d, parentPID = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, this->isMainResource(), this->isSynchronous(), m_parameters.parentPID);
185 },
186 [this] (ResourceRequest& request) {
187 if (this->canUseCache(request)) {
188 RELEASE_LOG_IF_ALLOWED("start: Checking cache for resource (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d, parentPID = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, this->isMainResource(), this->isSynchronous(), m_parameters.parentPID);
189 this->retrieveCacheEntry(request);
190 return;
191 }
192
193 this->startNetworkLoad(WTFMove(request), FirstLoad::Yes);
194 }
195 );
196 });
197 return;
198 }
199 // FIXME: Remove that code path once m_networkLoadChecker is used for all network loads.
200 if (canUseCache(originalRequest())) {
201 RELEASE_LOG_IF_ALLOWED("start: Checking cache for resource (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d, parentPID = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous(), m_parameters.parentPID);
202 retrieveCacheEntry(originalRequest());
203 return;
204 }
205
206 startNetworkLoad(ResourceRequest { originalRequest() }, FirstLoad::Yes);
207}
208
209void NetworkResourceLoader::retrieveCacheEntry(const ResourceRequest& request)
210{
211 ASSERT(canUseCache(request));
212
213 RefPtr<NetworkResourceLoader> loader(this);
214 if (isMainFrameLoad()) {
215 ASSERT(m_parameters.options.mode == FetchOptions::Mode::Navigate);
216 if (auto session = m_connection->networkProcess().networkSession(sessionID())) {
217 if (auto entry = session->prefetchCache().take(request.url())) {
218 if (!entry->redirectRequest.isNull()) {
219 auto maxAgeCap = validateCacheEntryForMaxAgeCapValidation(request, entry->redirectRequest, entry->response);
220 m_cache->storeRedirect(request, entry->response, entry->redirectRequest, maxAgeCap);
221 } else
222 m_cache->store(request, entry->response, entry->releaseBuffer(), nullptr);
223 }
224 }
225 }
226 m_cache->retrieve(request, { m_parameters.webPageID, m_parameters.webFrameID }, [this, loader = WTFMove(loader), request = ResourceRequest { request }](auto entry, auto info) mutable {
227 if (loader->hasOneRef()) {
228 // The loader has been aborted and is only held alive by this lambda.
229 return;
230 }
231
232 loader->logSlowCacheRetrieveIfNeeded(info);
233
234 if (!entry) {
235 RELEASE_LOG_IF_ALLOWED("retrieveCacheEntry: Resource not in cache (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
236 loader->startNetworkLoad(WTFMove(request), FirstLoad::Yes);
237 return;
238 }
239#if ENABLE(RESOURCE_LOAD_STATISTICS)
240 if (entry->hasReachedPrevalentResourceAgeCap()) {
241 RELEASE_LOG_IF_ALLOWED("retrieveCacheEntry: Resource has reached prevalent resource age cap (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
242 m_cacheEntryForMaxAgeCapValidation = WTFMove(entry);
243 ResourceRequest revalidationRequest = originalRequest();
244 loader->startNetworkLoad(WTFMove(revalidationRequest), FirstLoad::Yes);
245 return;
246 }
247#endif
248 if (entry->redirectRequest()) {
249 RELEASE_LOG_IF_ALLOWED("retrieveCacheEntry: Handling redirect (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
250 loader->dispatchWillSendRequestForCacheEntry(WTFMove(request), WTFMove(entry));
251 return;
252 }
253 if (loader->m_parameters.needsCertificateInfo && !entry->response().certificateInfo()) {
254 RELEASE_LOG_IF_ALLOWED("retrieveCacheEntry: Resource does not have required certificate (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
255 loader->startNetworkLoad(WTFMove(request), FirstLoad::Yes);
256 return;
257 }
258 if (entry->needsValidation() || request.cachePolicy() == WebCore::ResourceRequestCachePolicy::RefreshAnyCacheData) {
259 RELEASE_LOG_IF_ALLOWED("retrieveCacheEntry: Validating cache entry (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
260 loader->validateCacheEntry(WTFMove(entry));
261 return;
262 }
263 RELEASE_LOG_IF_ALLOWED("retrieveCacheEntry: Retrieved resource from cache (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
264 loader->didRetrieveCacheEntry(WTFMove(entry));
265 });
266}
267
268void NetworkResourceLoader::startNetworkLoad(ResourceRequest&& request, FirstLoad load)
269{
270 if (load == FirstLoad::Yes) {
271 RELEASE_LOG_IF_ALLOWED("startNetworkLoad: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isMainResource = %d, isSynchronous = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, isMainResource(), isSynchronous());
272
273 consumeSandboxExtensions();
274
275 if (isSynchronous() || m_parameters.maximumBufferingTime > 0_s)
276 m_bufferedData = SharedBuffer::create();
277
278 if (canUseCache(request))
279 m_bufferedDataForCache = SharedBuffer::create();
280 }
281
282 NetworkLoadParameters parameters = m_parameters;
283 parameters.networkActivityTracker = m_networkActivityTracker;
284 if (parameters.storedCredentialsPolicy == WebCore::StoredCredentialsPolicy::Use && m_networkLoadChecker)
285 parameters.storedCredentialsPolicy = m_networkLoadChecker->storedCredentialsPolicy();
286
287 if (request.url().protocolIsBlob())
288 parameters.blobFileReferences = m_connection->filesInBlob(originalRequest().url());
289
290 auto* networkSession = m_connection->networkProcess().networkSession(parameters.sessionID);
291 if (!networkSession && parameters.sessionID.isEphemeral()) {
292 m_connection->networkProcess().addWebsiteDataStore(WebsiteDataStoreParameters::privateSessionParameters(parameters.sessionID));
293 networkSession = m_connection->networkProcess().networkSession(parameters.sessionID);
294 }
295 if (!networkSession) {
296 WTFLogAlways("Attempted to create a NetworkLoad with a session (id=%" PRIu64 ") that does not exist.", parameters.sessionID.sessionID());
297 RELEASE_LOG_ERROR_IF_ALLOWED("startNetworkLoad: Attempted to create a NetworkLoad with a session that does not exist (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", sessionID=%" PRIu64 ")", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, parameters.sessionID.sessionID());
298 m_connection->networkProcess().logDiagnosticMessage(m_parameters.webPageID, WebCore::DiagnosticLoggingKeys::internalErrorKey(), WebCore::DiagnosticLoggingKeys::invalidSessionIDKey(), WebCore::ShouldSample::No);
299 didFailLoading(internalError(request.url()));
300 return;
301 }
302
303 parameters.request = WTFMove(request);
304 m_networkLoad = std::make_unique<NetworkLoad>(*this, &m_connection->blobRegistry(), WTFMove(parameters), *networkSession);
305
306 RELEASE_LOG_IF_ALLOWED("startNetworkLoad: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", description = %{public}s)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, m_networkLoad->description().utf8().data());
307}
308
309void NetworkResourceLoader::cleanup(LoadResult result)
310{
311 ASSERT(RunLoop::isMain());
312
313 m_connection->stopTrackingResourceLoad(m_parameters.identifier,
314 result == LoadResult::Success ? NetworkActivityTracker::CompletionCode::Success :
315 result == LoadResult::Failure ? NetworkActivityTracker::CompletionCode::Failure :
316 NetworkActivityTracker::CompletionCode::None);
317
318 m_bufferingTimer.stop();
319
320 invalidateSandboxExtensions();
321
322 m_networkLoad = nullptr;
323
324 // This will cause NetworkResourceLoader to be destroyed and therefore we do it last.
325 m_connection->didCleanupResourceLoader(*this);
326}
327
328void NetworkResourceLoader::convertToDownload(DownloadID downloadID, const ResourceRequest& request, const ResourceResponse& response)
329{
330 RELEASE_LOG(Loading, "Converting NetworkResourceLoader %p to download %" PRIu64 " (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")", this, downloadID.downloadID(), m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier);
331
332 // This can happen if the resource came from the disk cache.
333 if (!m_networkLoad) {
334 m_connection->networkProcess().downloadManager().startDownload(m_parameters.sessionID, downloadID, request);
335 abort();
336 return;
337 }
338
339 if (m_responseCompletionHandler)
340 m_connection->networkProcess().downloadManager().convertNetworkLoadToDownload(downloadID, std::exchange(m_networkLoad, nullptr), WTFMove(m_responseCompletionHandler), WTFMove(m_fileReferences), request, response);
341}
342
343void NetworkResourceLoader::abort()
344{
345 ASSERT(RunLoop::isMain());
346
347 RELEASE_LOG_IF_ALLOWED("abort: Canceling resource load (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")",
348 m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier);
349
350 if (m_parameters.options.keepAlive && m_response.isNull() && !m_isKeptAlive) {
351 m_isKeptAlive = true;
352 m_connection->transferKeptAliveLoad(*this);
353 return;
354 }
355
356 if (m_networkLoad) {
357 if (canUseCache(m_networkLoad->currentRequest())) {
358 // We might already have used data from this incomplete load. Ensure older versions don't remain in the cache after cancel.
359 if (!m_response.isNull())
360 m_cache->remove(m_networkLoad->currentRequest());
361 }
362 m_networkLoad->cancel();
363 }
364
365 cleanup(LoadResult::Cancel);
366}
367
368bool NetworkResourceLoader::shouldInterruptLoadForXFrameOptions(const String& xFrameOptions, const URL& url)
369{
370 if (isMainFrameLoad())
371 return false;
372
373 switch (parseXFrameOptionsHeader(xFrameOptions)) {
374 case XFrameOptionsNone:
375 case XFrameOptionsAllowAll:
376 return false;
377 case XFrameOptionsDeny:
378 return true;
379 case XFrameOptionsSameOrigin: {
380 auto origin = SecurityOrigin::create(url);
381 auto topFrameOrigin = m_parameters.frameAncestorOrigins.last();
382 if (!origin->isSameSchemeHostPort(*topFrameOrigin))
383 return true;
384 for (auto& ancestorOrigin : m_parameters.frameAncestorOrigins) {
385 if (!origin->isSameSchemeHostPort(*ancestorOrigin))
386 return true;
387 }
388 return false;
389 }
390 case XFrameOptionsConflict: {
391 String errorMessage = "Multiple 'X-Frame-Options' headers with conflicting values ('" + xFrameOptions + "') encountered when loading '" + url.stringCenterEllipsizedToLength() + "'. Falling back to 'DENY'.";
392 send(Messages::WebPage::AddConsoleMessage { m_parameters.webFrameID, MessageSource::JS, MessageLevel::Error, errorMessage, identifier() }, m_parameters.webPageID);
393 return true;
394 }
395 case XFrameOptionsInvalid: {
396 String errorMessage = "Invalid 'X-Frame-Options' header encountered when loading '" + url.stringCenterEllipsizedToLength() + "': '" + xFrameOptions + "' is not a recognized directive. The header will be ignored.";
397 send(Messages::WebPage::AddConsoleMessage { m_parameters.webFrameID, MessageSource::JS, MessageLevel::Error, errorMessage, identifier() }, m_parameters.webPageID);
398 return false;
399 }
400 }
401 ASSERT_NOT_REACHED();
402 return false;
403}
404
405bool NetworkResourceLoader::shouldInterruptLoadForCSPFrameAncestorsOrXFrameOptions(const ResourceResponse& response)
406{
407 ASSERT(isMainResource());
408
409#if USE(QUICK_LOOK)
410 if (PreviewConverter::supportsMIMEType(response.mimeType()))
411 return false;
412#endif
413
414 auto url = response.url();
415 ContentSecurityPolicy contentSecurityPolicy { URL { url }, this };
416 contentSecurityPolicy.didReceiveHeaders(ContentSecurityPolicyResponseHeaders { response }, originalRequest().httpReferrer());
417 if (!contentSecurityPolicy.allowFrameAncestors(m_parameters.frameAncestorOrigins, url))
418 return true;
419
420 if (!contentSecurityPolicy.overridesXFrameOptions()) {
421 String xFrameOptions = m_response.httpHeaderField(HTTPHeaderName::XFrameOptions);
422 if (!xFrameOptions.isNull() && shouldInterruptLoadForXFrameOptions(xFrameOptions, response.url())) {
423 String errorMessage = "Refused to display '" + response.url().stringCenterEllipsizedToLength() + "' in a frame because it set 'X-Frame-Options' to '" + xFrameOptions + "'.";
424 send(Messages::WebPage::AddConsoleMessage { m_parameters.webFrameID, MessageSource::Security, MessageLevel::Error, errorMessage, identifier() }, m_parameters.webPageID);
425 return true;
426 }
427 }
428 return false;
429}
430
431void NetworkResourceLoader::didReceiveResponse(ResourceResponse&& receivedResponse, ResponseCompletionHandler&& completionHandler)
432{
433 RELEASE_LOG_IF_ALLOWED("didReceiveResponse: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", httpStatusCode = %d, length = %" PRId64 ")", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, receivedResponse.httpStatusCode(), receivedResponse.expectedContentLength());
434
435 m_response = WTFMove(receivedResponse);
436
437 if (shouldCaptureExtraNetworkLoadMetrics() && m_networkLoadChecker) {
438 auto information = m_networkLoadChecker->takeNetworkLoadInformation();
439 information.response = m_response;
440 m_connection->addNetworkLoadInformation(identifier(), WTFMove(information));
441 }
442
443 // For multipart/x-mixed-replace didReceiveResponseAsync gets called multiple times and buffering would require special handling.
444 if (!isSynchronous() && m_response.isMultipart())
445 m_bufferedData = nullptr;
446
447 if (m_response.isMultipart())
448 m_bufferedDataForCache = nullptr;
449
450 if (m_cacheEntryForValidation) {
451 bool validationSucceeded = m_response.httpStatusCode() == 304; // 304 Not Modified
452 if (validationSucceeded) {
453 m_cacheEntryForValidation = m_cache->update(originalRequest(), { m_parameters.webPageID, m_parameters.webFrameID }, *m_cacheEntryForValidation, m_response);
454 // If the request was conditional then this revalidation was not triggered by the network cache and we pass the 304 response to WebCore.
455 if (originalRequest().isConditional())
456 m_cacheEntryForValidation = nullptr;
457 } else
458 m_cacheEntryForValidation = nullptr;
459 }
460 if (m_cacheEntryForValidation)
461 return completionHandler(PolicyAction::Use);
462
463 if (isMainResource() && shouldInterruptLoadForCSPFrameAncestorsOrXFrameOptions(m_response)) {
464 auto response = sanitizeResponseIfPossible(ResourceResponse { m_response }, ResourceResponse::SanitizationType::CrossOriginSafe);
465 send(Messages::WebResourceLoader::StopLoadingAfterXFrameOptionsOrContentSecurityPolicyDenied { response });
466 return completionHandler(PolicyAction::Ignore);
467 }
468
469 if (m_networkLoadChecker) {
470 auto error = m_networkLoadChecker->validateResponse(m_response);
471 if (!error.isNull()) {
472 RunLoop::main().dispatch([protectedThis = makeRef(*this), error = WTFMove(error)] {
473 if (protectedThis->m_networkLoad)
474 protectedThis->didFailLoading(error);
475 });
476 return completionHandler(PolicyAction::Ignore);
477 }
478 }
479
480 auto response = sanitizeResponseIfPossible(ResourceResponse { m_response }, ResourceResponse::SanitizationType::CrossOriginSafe);
481 if (isSynchronous()) {
482 m_synchronousLoadData->response = WTFMove(response);
483 return completionHandler(PolicyAction::Use);
484 }
485
486 if (isCrossOriginPrefetch())
487 return completionHandler(PolicyAction::Use);
488
489 // We wait to receive message NetworkResourceLoader::ContinueDidReceiveResponse before continuing a load for
490 // a main resource because the embedding client must decide whether to allow the load.
491 bool willWaitForContinueDidReceiveResponse = isMainResource();
492 send(Messages::WebResourceLoader::DidReceiveResponse { response, willWaitForContinueDidReceiveResponse });
493
494 if (willWaitForContinueDidReceiveResponse) {
495 m_responseCompletionHandler = WTFMove(completionHandler);
496 return;
497 }
498
499 if (m_isKeptAlive) {
500 m_responseCompletionHandler = WTFMove(completionHandler);
501 RunLoop::main().dispatch([protectedThis = makeRef(*this)] {
502 protectedThis->didFinishLoading(NetworkLoadMetrics { });
503 });
504 return;
505 }
506
507 completionHandler(PolicyAction::Use);
508}
509
510void NetworkResourceLoader::didReceiveBuffer(Ref<SharedBuffer>&& buffer, int reportedEncodedDataLength)
511{
512 if (!m_numBytesReceived)
513 RELEASE_LOG_IF_ALLOWED("didReceiveBuffer: Started receiving data (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier);
514 m_numBytesReceived += buffer->size();
515
516 ASSERT(!m_cacheEntryForValidation);
517
518 if (m_bufferedDataForCache) {
519 // Prevent memory growth in case of streaming data.
520 const size_t maximumCacheBufferSize = 10 * 1024 * 1024;
521 if (m_bufferedDataForCache->size() + buffer->size() <= maximumCacheBufferSize)
522 m_bufferedDataForCache->append(buffer.get());
523 else
524 m_bufferedDataForCache = nullptr;
525 }
526 if (isCrossOriginPrefetch())
527 return;
528 // FIXME: At least on OS X Yosemite we always get -1 from the resource handle.
529 unsigned encodedDataLength = reportedEncodedDataLength >= 0 ? reportedEncodedDataLength : buffer->size();
530
531 if (m_bufferedData) {
532 m_bufferedData->append(buffer.get());
533 m_bufferedDataEncodedDataLength += encodedDataLength;
534 startBufferingTimerIfNeeded();
535 return;
536 }
537 sendBuffer(buffer, encodedDataLength);
538}
539
540void NetworkResourceLoader::didFinishLoading(const NetworkLoadMetrics& networkLoadMetrics)
541{
542 RELEASE_LOG_IF_ALLOWED("didFinishLoading: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", length = %zd)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, m_numBytesReceived);
543
544 if (shouldCaptureExtraNetworkLoadMetrics())
545 m_connection->addNetworkLoadInformationMetrics(identifier(), networkLoadMetrics);
546
547 if (m_cacheEntryForValidation) {
548 // 304 Not Modified
549 ASSERT(m_response.httpStatusCode() == 304);
550 LOG(NetworkCache, "(NetworkProcess) revalidated");
551 didRetrieveCacheEntry(WTFMove(m_cacheEntryForValidation));
552 return;
553 }
554
555#if ENABLE(RESOURCE_LOAD_STATISTICS) && !RELEASE_LOG_DISABLED
556 if (shouldLogCookieInformation(m_connection, sessionID()))
557 logCookieInformation();
558#endif
559
560 if (isSynchronous())
561 sendReplyToSynchronousRequest(*m_synchronousLoadData, m_bufferedData.get());
562 else {
563 if (m_bufferedData && !m_bufferedData->isEmpty()) {
564 // FIXME: Pass a real value or remove the encoded data size feature.
565 sendBuffer(*m_bufferedData, -1);
566 }
567 send(Messages::WebResourceLoader::DidFinishResourceLoad(networkLoadMetrics));
568 }
569
570 tryStoreAsCacheEntry();
571
572 cleanup(LoadResult::Success);
573}
574
575void NetworkResourceLoader::didFailLoading(const ResourceError& error)
576{
577 RELEASE_LOG_IF_ALLOWED("didFailLoading: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ", isTimeout = %d, isCancellation = %d, isAccessControl = %d, errCode = %d)", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier, error.isTimeout(), error.isCancellation(), error.isAccessControl(), error.errorCode());
578
579 if (shouldCaptureExtraNetworkLoadMetrics())
580 m_connection->removeNetworkLoadInformation(identifier());
581
582 ASSERT(!error.isNull());
583
584 m_cacheEntryForValidation = nullptr;
585
586 if (isSynchronous()) {
587 m_synchronousLoadData->error = error;
588 sendReplyToSynchronousRequest(*m_synchronousLoadData, nullptr);
589 } else if (auto* connection = messageSenderConnection())
590 connection->send(Messages::WebResourceLoader::DidFailResourceLoad(error), messageSenderDestinationID());
591
592 cleanup(LoadResult::Failure);
593}
594
595void NetworkResourceLoader::didBlockAuthenticationChallenge()
596{
597 send(Messages::WebResourceLoader::DidBlockAuthenticationChallenge());
598}
599
600Optional<Seconds> NetworkResourceLoader::validateCacheEntryForMaxAgeCapValidation(const ResourceRequest& request, const ResourceRequest& redirectRequest, const ResourceResponse& redirectResponse)
601{
602#if ENABLE(RESOURCE_LOAD_STATISTICS)
603 bool existingCacheEntryMatchesNewResponse = false;
604 if (m_cacheEntryForMaxAgeCapValidation) {
605 ASSERT(redirectResponse.source() == ResourceResponse::Source::Network);
606 ASSERT(redirectResponse.isRedirection());
607 if (redirectResponse.httpHeaderField(WebCore::HTTPHeaderName::Location) == m_cacheEntryForMaxAgeCapValidation->response().httpHeaderField(WebCore::HTTPHeaderName::Location))
608 existingCacheEntryMatchesNewResponse = true;
609
610 m_cache->remove(m_cacheEntryForMaxAgeCapValidation->key());
611 m_cacheEntryForMaxAgeCapValidation = nullptr;
612 }
613
614 if (!existingCacheEntryMatchesNewResponse) {
615 if (auto* networkStorageSession = m_connection->networkProcess().storageSession(sessionID()))
616 return networkStorageSession->maxAgeCacheCap(request);
617 }
618#endif
619 return WTF::nullopt;
620}
621
622void NetworkResourceLoader::willSendRedirectedRequest(ResourceRequest&& request, ResourceRequest&& redirectRequest, ResourceResponse&& redirectResponse)
623{
624 ++m_redirectCount;
625
626 Optional<AdClickAttribution::Conversion> adClickConversion;
627 if (!sessionID().isEphemeral())
628 adClickConversion = AdClickAttribution::parseConversionRequest(redirectRequest.url());
629
630 auto maxAgeCap = validateCacheEntryForMaxAgeCapValidation(request, redirectRequest, redirectResponse);
631 if (redirectResponse.source() == ResourceResponse::Source::Network && canUseCachedRedirect(request))
632 m_cache->storeRedirect(request, redirectResponse, redirectRequest, maxAgeCap);
633
634 if (m_networkLoadChecker) {
635 if (adClickConversion)
636 m_networkLoadChecker->enableContentExtensionsCheck();
637 m_networkLoadChecker->storeRedirectionIfNeeded(request, redirectResponse);
638 m_networkLoadChecker->checkRedirection(WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse), this, [protectedThis = makeRef(*this), this, storedCredentialsPolicy = m_networkLoadChecker->storedCredentialsPolicy(), adClickConversion = WTFMove(adClickConversion)](auto&& result) mutable {
639 if (!result.has_value()) {
640 if (result.error().isCancellation())
641 return;
642
643 this->didFailLoading(result.error());
644 return;
645 }
646
647 if (m_parameters.options.redirect == FetchOptions::Redirect::Manual) {
648 this->didFinishWithRedirectResponse(WTFMove(result->request), WTFMove(result->redirectRequest), WTFMove(result->redirectResponse));
649 return;
650 }
651
652 if (this->isSynchronous()) {
653 if (storedCredentialsPolicy != m_networkLoadChecker->storedCredentialsPolicy()) {
654 // We need to restart the load to update the session according the new credential policy.
655 this->restartNetworkLoad(WTFMove(result->redirectRequest));
656 return;
657 }
658
659 // We do not support prompting for credentials for synchronous loads. If we ever change this policy then
660 // we need to take care to prompt if and only if request and redirectRequest are not mixed content.
661 this->continueWillSendRequest(WTFMove(result->redirectRequest), false);
662 return;
663 }
664
665 m_shouldRestartLoad = storedCredentialsPolicy != m_networkLoadChecker->storedCredentialsPolicy();
666 this->continueWillSendRedirectedRequest(WTFMove(result->request), WTFMove(result->redirectRequest), WTFMove(result->redirectResponse), WTFMove(adClickConversion));
667 });
668 return;
669 }
670 continueWillSendRedirectedRequest(WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse), WTFMove(adClickConversion));
671}
672
673void NetworkResourceLoader::continueWillSendRedirectedRequest(ResourceRequest&& request, ResourceRequest&& redirectRequest, ResourceResponse&& redirectResponse, Optional<AdClickAttribution::Conversion>&& adClickConversion)
674{
675 ASSERT(!isSynchronous());
676
677 if (m_isKeptAlive) {
678 continueWillSendRequest(WTFMove(redirectRequest), false);
679 return;
680 }
681
682 NetworkSession* networkSession = nullptr;
683 if (adClickConversion && (networkSession = m_connection->networkProcess().networkSession(sessionID())))
684 networkSession->handleAdClickAttributionConversion(WTFMove(*adClickConversion), request.url(), redirectRequest);
685
686 send(Messages::WebResourceLoader::WillSendRequest(redirectRequest, sanitizeResponseIfPossible(WTFMove(redirectResponse), ResourceResponse::SanitizationType::Redirection)));
687}
688
689void NetworkResourceLoader::didFinishWithRedirectResponse(WebCore::ResourceRequest&& request, WebCore::ResourceRequest&& redirectRequest, ResourceResponse&& redirectResponse)
690{
691 redirectResponse.setType(ResourceResponse::Type::Opaqueredirect);
692 if (!isCrossOriginPrefetch())
693 didReceiveResponse(WTFMove(redirectResponse), [] (auto) { });
694 else if (auto session = m_connection->networkProcess().networkSession(sessionID()))
695 session->prefetchCache().storeRedirect(m_networkLoad->currentRequest().url(), WTFMove(redirectResponse), WTFMove(redirectRequest));
696
697 WebCore::NetworkLoadMetrics networkLoadMetrics;
698 networkLoadMetrics.markComplete();
699 networkLoadMetrics.responseBodyBytesReceived = 0;
700 networkLoadMetrics.responseBodyDecodedSize = 0;
701 send(Messages::WebResourceLoader::DidFinishResourceLoad { networkLoadMetrics });
702
703 cleanup(LoadResult::Success);
704}
705
706ResourceResponse NetworkResourceLoader::sanitizeResponseIfPossible(ResourceResponse&& response, ResourceResponse::SanitizationType type)
707{
708 if (m_parameters.shouldRestrictHTTPResponseAccess)
709 response.sanitizeHTTPHeaderFields(type);
710
711 return WTFMove(response);
712}
713
714void NetworkResourceLoader::restartNetworkLoad(WebCore::ResourceRequest&& newRequest)
715{
716 if (m_networkLoad)
717 m_networkLoad->cancel();
718
719 startNetworkLoad(WTFMove(newRequest), FirstLoad::No);
720}
721
722void NetworkResourceLoader::continueWillSendRequest(ResourceRequest&& newRequest, bool isAllowedToAskUserForCredentials)
723{
724 if (m_shouldRestartLoad) {
725 m_shouldRestartLoad = false;
726
727 if (m_networkLoad)
728 m_networkLoad->updateRequestAfterRedirection(newRequest);
729
730 restartNetworkLoad(WTFMove(newRequest));
731 return;
732 }
733
734 if (m_networkLoadChecker) {
735 // FIXME: We should be doing this check when receiving the redirection and not allow about protocol as per fetch spec.
736 if (!newRequest.url().protocolIsInHTTPFamily() && !newRequest.url().protocolIsAbout() && m_redirectCount) {
737 didFailLoading(ResourceError { String { }, 0, newRequest.url(), "Redirection to URL with a scheme that is not HTTP(S)"_s, ResourceError::Type::AccessControl });
738 return;
739 }
740 }
741
742 RELEASE_LOG_IF_ALLOWED("continueWillSendRequest: (pageID = %" PRIu64 ", frameID = %" PRIu64 ", resourceID = %" PRIu64 ")", m_parameters.webPageID.toUInt64(), m_parameters.webFrameID, m_parameters.identifier);
743
744 m_isAllowedToAskUserForCredentials = isAllowedToAskUserForCredentials;
745
746 // If there is a match in the network cache, we need to reuse the original cache policy and partition.
747 newRequest.setCachePolicy(originalRequest().cachePolicy());
748 newRequest.setCachePartition(originalRequest().cachePartition());
749
750 if (m_isWaitingContinueWillSendRequestForCachedRedirect) {
751 m_isWaitingContinueWillSendRequestForCachedRedirect = false;
752
753 LOG(NetworkCache, "(NetworkProcess) Retrieving cached redirect");
754
755 if (canUseCachedRedirect(newRequest))
756 retrieveCacheEntry(newRequest);
757 else
758 startNetworkLoad(WTFMove(newRequest), FirstLoad::Yes);
759
760 return;
761 }
762
763 if (m_networkLoad)
764 m_networkLoad->continueWillSendRequest(WTFMove(newRequest));
765}
766
767void NetworkResourceLoader::continueDidReceiveResponse()
768{
769 if (m_cacheEntryWaitingForContinueDidReceiveResponse) {
770 sendResultForCacheEntry(WTFMove(m_cacheEntryWaitingForContinueDidReceiveResponse));
771 cleanup(LoadResult::Success);
772 return;
773 }
774
775 if (m_responseCompletionHandler)
776 m_responseCompletionHandler(PolicyAction::Use);
777}
778
779void NetworkResourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
780{
781 if (!isSynchronous())
782 send(Messages::WebResourceLoader::DidSendData(bytesSent, totalBytesToBeSent));
783}
784
785void NetworkResourceLoader::startBufferingTimerIfNeeded()
786{
787 if (isSynchronous())
788 return;
789 if (m_bufferingTimer.isActive())
790 return;
791 m_bufferingTimer.startOneShot(m_parameters.maximumBufferingTime);
792}
793
794void NetworkResourceLoader::bufferingTimerFired()
795{
796 ASSERT(m_bufferedData);
797 ASSERT(m_networkLoad);
798
799 if (m_bufferedData->isEmpty())
800 return;
801
802 send(Messages::WebResourceLoader::DidReceiveData({ *m_bufferedData }, m_bufferedDataEncodedDataLength));
803
804 m_bufferedData = SharedBuffer::create();
805 m_bufferedDataEncodedDataLength = 0;
806}
807
808void NetworkResourceLoader::sendBuffer(SharedBuffer& buffer, size_t encodedDataLength)
809{
810 ASSERT(!isSynchronous());
811
812 send(Messages::WebResourceLoader::DidReceiveData({ buffer }, encodedDataLength));
813}
814
815void NetworkResourceLoader::tryStoreAsCacheEntry()
816{
817 if (!canUseCache(m_networkLoad->currentRequest()))
818 return;
819 if (!m_bufferedDataForCache)
820 return;
821
822 if (isCrossOriginPrefetch()) {
823 if (auto session = m_connection->networkProcess().networkSession(sessionID()))
824 session->prefetchCache().store(m_networkLoad->currentRequest().url(), WTFMove(m_response), WTFMove(m_bufferedDataForCache));
825 return;
826 }
827 m_cache->store(m_networkLoad->currentRequest(), m_response, WTFMove(m_bufferedDataForCache), [loader = makeRef(*this)](auto& mappedBody) mutable {
828#if ENABLE(SHAREABLE_RESOURCE)
829 if (mappedBody.shareableResourceHandle.isNull())
830 return;
831 LOG(NetworkCache, "(NetworkProcess) sending DidCacheResource");
832 loader->send(Messages::NetworkProcessConnection::DidCacheResource(loader->originalRequest(), mappedBody.shareableResourceHandle, loader->sessionID()));
833#endif
834 });
835}
836
837void NetworkResourceLoader::didRetrieveCacheEntry(std::unique_ptr<NetworkCache::Entry> entry)
838{
839 auto response = entry->response();
840
841 if (isMainResource() && shouldInterruptLoadForCSPFrameAncestorsOrXFrameOptions(response)) {
842 response = sanitizeResponseIfPossible(WTFMove(response), ResourceResponse::SanitizationType::CrossOriginSafe);
843 send(Messages::WebResourceLoader::StopLoadingAfterXFrameOptionsOrContentSecurityPolicyDenied { response });
844 return;
845 }
846 if (m_networkLoadChecker) {
847 auto error = m_networkLoadChecker->validateResponse(response);
848 if (!error.isNull()) {
849 didFailLoading(error);
850 return;
851 }
852 }
853
854 response = sanitizeResponseIfPossible(WTFMove(response), ResourceResponse::SanitizationType::CrossOriginSafe);
855 if (isSynchronous()) {
856 m_synchronousLoadData->response = WTFMove(response);
857 sendReplyToSynchronousRequest(*m_synchronousLoadData, entry->buffer());
858 cleanup(LoadResult::Success);
859 return;
860 }
861
862 bool needsContinueDidReceiveResponseMessage = isMainResource();
863 send(Messages::WebResourceLoader::DidReceiveResponse { response, needsContinueDidReceiveResponseMessage });
864
865 if (needsContinueDidReceiveResponseMessage)
866 m_cacheEntryWaitingForContinueDidReceiveResponse = WTFMove(entry);
867 else {
868 sendResultForCacheEntry(WTFMove(entry));
869 cleanup(LoadResult::Success);
870 }
871}
872
873void NetworkResourceLoader::sendResultForCacheEntry(std::unique_ptr<NetworkCache::Entry> entry)
874{
875#if ENABLE(SHAREABLE_RESOURCE)
876 if (!entry->shareableResourceHandle().isNull()) {
877 send(Messages::WebResourceLoader::DidReceiveResource(entry->shareableResourceHandle()));
878 return;
879 }
880#endif
881
882#if ENABLE(RESOURCE_LOAD_STATISTICS) && !RELEASE_LOG_DISABLED
883 if (shouldLogCookieInformation(m_connection, sessionID()))
884 logCookieInformation();
885#endif
886
887 WebCore::NetworkLoadMetrics networkLoadMetrics;
888 networkLoadMetrics.markComplete();
889 networkLoadMetrics.requestHeaderBytesSent = 0;
890 networkLoadMetrics.requestBodyBytesSent = 0;
891 networkLoadMetrics.responseHeaderBytesReceived = 0;
892 networkLoadMetrics.responseBodyBytesReceived = 0;
893 networkLoadMetrics.responseBodyDecodedSize = 0;
894
895 sendBuffer(*entry->buffer(), entry->buffer()->size());
896 send(Messages::WebResourceLoader::DidFinishResourceLoad(networkLoadMetrics));
897}
898
899void NetworkResourceLoader::validateCacheEntry(std::unique_ptr<NetworkCache::Entry> entry)
900{
901 ASSERT(!m_networkLoad);
902
903 // If the request is already conditional then the revalidation was not triggered by the disk cache
904 // and we should not overwrite the existing conditional headers.
905 ResourceRequest revalidationRequest = originalRequest();
906 if (!revalidationRequest.isConditional()) {
907 String eTag = entry->response().httpHeaderField(HTTPHeaderName::ETag);
908 String lastModified = entry->response().httpHeaderField(HTTPHeaderName::LastModified);
909 if (!eTag.isEmpty())
910 revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
911 if (!lastModified.isEmpty())
912 revalidationRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
913 }
914
915 m_cacheEntryForValidation = WTFMove(entry);
916
917 startNetworkLoad(WTFMove(revalidationRequest), FirstLoad::Yes);
918}
919
920void NetworkResourceLoader::dispatchWillSendRequestForCacheEntry(ResourceRequest&& request, std::unique_ptr<NetworkCache::Entry>&& entry)
921{
922 ASSERT(entry->redirectRequest());
923 ASSERT(!m_isWaitingContinueWillSendRequestForCachedRedirect);
924
925 LOG(NetworkCache, "(NetworkProcess) Executing cached redirect");
926
927 m_isWaitingContinueWillSendRequestForCachedRedirect = true;
928 willSendRedirectedRequest(WTFMove(request), ResourceRequest { *entry->redirectRequest() }, ResourceResponse { entry->response() });
929}
930
931IPC::Connection* NetworkResourceLoader::messageSenderConnection() const
932{
933 return &connectionToWebProcess().connection();
934}
935
936void NetworkResourceLoader::consumeSandboxExtensions()
937{
938 ASSERT(!m_didConsumeSandboxExtensions);
939
940 for (auto& extension : m_parameters.requestBodySandboxExtensions)
941 extension->consume();
942
943 if (auto& extension = m_parameters.resourceSandboxExtension)
944 extension->consume();
945
946 for (auto& fileReference : m_fileReferences)
947 fileReference->prepareForFileAccess();
948
949 m_didConsumeSandboxExtensions = true;
950}
951
952void NetworkResourceLoader::invalidateSandboxExtensions()
953{
954 if (m_didConsumeSandboxExtensions) {
955 for (auto& extension : m_parameters.requestBodySandboxExtensions)
956 extension->revoke();
957 if (auto& extension = m_parameters.resourceSandboxExtension)
958 extension->revoke();
959 for (auto& fileReference : m_fileReferences)
960 fileReference->revokeFileAccess();
961
962 m_didConsumeSandboxExtensions = false;
963 }
964
965 m_fileReferences.clear();
966}
967
968bool NetworkResourceLoader::isAlwaysOnLoggingAllowed() const
969{
970 if (m_connection->networkProcess().sessionIsControlledByAutomation(sessionID()))
971 return true;
972
973 return sessionID().isAlwaysOnLoggingAllowed();
974}
975
976bool NetworkResourceLoader::shouldCaptureExtraNetworkLoadMetrics() const
977{
978 return m_shouldCaptureExtraNetworkLoadMetrics;
979}
980
981#if ENABLE(RESOURCE_LOAD_STATISTICS) && !RELEASE_LOG_DISABLED
982bool NetworkResourceLoader::shouldLogCookieInformation(NetworkConnectionToWebProcess& connection, const PAL::SessionID& sessionID)
983{
984 if (auto session = connection.networkProcess().networkSession(sessionID))
985 return session->shouldLogCookieInformation();
986 return false;
987}
988
989static String escapeForJSON(String s)
990{
991 return s.replace('\\', "\\\\").replace('"', "\\\"");
992}
993
994static String escapeIDForJSON(const Optional<uint64_t>& value)
995{
996 return value ? String::number(value.value()) : String("None"_s);
997};
998
999static String escapeIDForJSON(const Optional<PageIdentifier>& value)
1000{
1001 return value ? String::number(value->toUInt64()) : String("None"_s);
1002};
1003
1004void NetworkResourceLoader::logCookieInformation() const
1005{
1006 ASSERT(shouldLogCookieInformation(m_connection, sessionID()));
1007
1008 auto* networkStorageSession = m_connection->networkProcess().storageSession(sessionID());
1009 ASSERT(networkStorageSession);
1010
1011 logCookieInformation(m_connection, "NetworkResourceLoader", reinterpret_cast<const void*>(this), *networkStorageSession, originalRequest().firstPartyForCookies(), SameSiteInfo::create(originalRequest()), originalRequest().url(), originalRequest().httpReferrer(), frameID(), pageID(), identifier());
1012}
1013
1014static void logBlockedCookieInformation(NetworkConnectionToWebProcess& connection, const String& label, const void* loggedObject, const WebCore::NetworkStorageSession& networkStorageSession, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, const String& referrer, Optional<uint64_t> frameID, Optional<PageIdentifier> pageID, Optional<uint64_t> identifier)
1015{
1016 ASSERT(NetworkResourceLoader::shouldLogCookieInformation(connection, networkStorageSession.sessionID()));
1017
1018 auto escapedURL = escapeForJSON(url.string());
1019 auto escapedFirstParty = escapeForJSON(firstParty.string());
1020 auto escapedFrameID = escapeIDForJSON(frameID);
1021 auto escapedPageID = escapeIDForJSON(pageID);
1022 auto escapedIdentifier = escapeIDForJSON(identifier);
1023 auto escapedReferrer = escapeForJSON(referrer);
1024
1025#define LOCAL_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(networkStorageSession.sessionID().isAlwaysOnLoggingAllowed(), Network, "%p - %s::" fmt, loggedObject, label.utf8().data(), ##__VA_ARGS__)
1026#define LOCAL_LOG(str, ...) \
1027 LOCAL_LOG_IF_ALLOWED("logCookieInformation: BLOCKED cookie access for pageID = %s, frameID = %s, resourceID = %s, firstParty = %s: " str, escapedPageID.utf8().data(), escapedFrameID.utf8().data(), escapedIdentifier.utf8().data(), escapedFirstParty.utf8().data(), ##__VA_ARGS__)
1028
1029 LOCAL_LOG(R"({ "url": "%{public}s",)", escapedURL.utf8().data());
1030 LOCAL_LOG(R"( "partition": "%{public}s",)", "BLOCKED");
1031 LOCAL_LOG(R"( "hasStorageAccess": %{public}s,)", "false");
1032 LOCAL_LOG(R"( "referer": "%{public}s",)", escapedReferrer.utf8().data());
1033 LOCAL_LOG(R"( "isSameSite": "%{public}s",)", sameSiteInfo.isSameSite ? "true" : "false");
1034 LOCAL_LOG(R"( "isTopSite": "%{public}s",)", sameSiteInfo.isTopSite ? "true" : "false");
1035 LOCAL_LOG(R"( "cookies": [])");
1036 LOCAL_LOG(R"( })");
1037#undef LOCAL_LOG
1038#undef LOCAL_LOG_IF_ALLOWED
1039}
1040
1041static void logCookieInformationInternal(NetworkConnectionToWebProcess& connection, const String& label, const void* loggedObject, const WebCore::NetworkStorageSession& networkStorageSession, const URL& firstParty, const WebCore::SameSiteInfo& sameSiteInfo, const URL& url, const String& referrer, Optional<uint64_t> frameID, Optional<PageIdentifier> pageID, Optional<uint64_t> identifier)
1042{
1043 ASSERT(NetworkResourceLoader::shouldLogCookieInformation(connection, networkStorageSession.sessionID()));
1044
1045 Vector<WebCore::Cookie> cookies;
1046 if (!networkStorageSession.getRawCookies(firstParty, sameSiteInfo, url, frameID, pageID, cookies))
1047 return;
1048
1049 auto escapedURL = escapeForJSON(url.string());
1050 auto escapedPartition = escapeForJSON(emptyString());
1051 auto escapedReferrer = escapeForJSON(referrer);
1052 auto escapedFrameID = escapeIDForJSON(frameID);
1053 auto escapedPageID = escapeIDForJSON(pageID);
1054 auto escapedIdentifier = escapeIDForJSON(identifier);
1055 bool hasStorageAccess = (frameID && pageID) ? networkStorageSession.hasStorageAccess(WebCore::RegistrableDomain { url }, WebCore::RegistrableDomain { firstParty }, frameID.value(), pageID.value()) : false;
1056
1057#define LOCAL_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(networkStorageSession.sessionID().isAlwaysOnLoggingAllowed(), Network, "%p - %s::" fmt, loggedObject, label.utf8().data(), ##__VA_ARGS__)
1058#define LOCAL_LOG(str, ...) \
1059 LOCAL_LOG_IF_ALLOWED("logCookieInformation: pageID = %s, frameID = %s, resourceID = %s: " str, escapedPageID.utf8().data(), escapedFrameID.utf8().data(), escapedIdentifier.utf8().data(), ##__VA_ARGS__)
1060
1061 LOCAL_LOG(R"({ "url": "%{public}s",)", escapedURL.utf8().data());
1062 LOCAL_LOG(R"( "partition": "%{public}s",)", escapedPartition.utf8().data());
1063 LOCAL_LOG(R"( "hasStorageAccess": %{public}s,)", hasStorageAccess ? "true" : "false");
1064 LOCAL_LOG(R"( "referer": "%{public}s",)", escapedReferrer.utf8().data());
1065 LOCAL_LOG(R"( "isSameSite": "%{public}s",)", sameSiteInfo.isSameSite ? "true" : "false");
1066 LOCAL_LOG(R"( "isTopSite": "%{public}s",)", sameSiteInfo.isTopSite ? "true" : "false");
1067 LOCAL_LOG(R"( "cookies": [)");
1068
1069 auto size = cookies.size();
1070 decltype(size) count = 0;
1071 for (const auto& cookie : cookies) {
1072 const char* trailingComma = ",";
1073 if (++count == size)
1074 trailingComma = "";
1075
1076 auto escapedName = escapeForJSON(cookie.name);
1077 auto escapedValue = escapeForJSON(cookie.value);
1078 auto escapedDomain = escapeForJSON(cookie.domain);
1079 auto escapedPath = escapeForJSON(cookie.path);
1080 auto escapedComment = escapeForJSON(cookie.comment);
1081 auto escapedCommentURL = escapeForJSON(cookie.commentURL.string());
1082 // FIXME: Log Same-Site policy for each cookie. See <https://bugs.webkit.org/show_bug.cgi?id=184894>.
1083
1084 LOCAL_LOG(R"( { "name": "%{public}s",)", escapedName.utf8().data());
1085 LOCAL_LOG(R"( "value": "%{public}s",)", escapedValue.utf8().data());
1086 LOCAL_LOG(R"( "domain": "%{public}s",)", escapedDomain.utf8().data());
1087 LOCAL_LOG(R"( "path": "%{public}s",)", escapedPath.utf8().data());
1088 LOCAL_LOG(R"( "created": %f,)", cookie.created);
1089 LOCAL_LOG(R"( "expires": %f,)", cookie.expires);
1090 LOCAL_LOG(R"( "httpOnly": %{public}s,)", cookie.httpOnly ? "true" : "false");
1091 LOCAL_LOG(R"( "secure": %{public}s,)", cookie.secure ? "true" : "false");
1092 LOCAL_LOG(R"( "session": %{public}s,)", cookie.session ? "true" : "false");
1093 LOCAL_LOG(R"( "comment": "%{public}s",)", escapedComment.utf8().data());
1094 LOCAL_LOG(R"( "commentURL": "%{public}s")", escapedCommentURL.utf8().data());
1095 LOCAL_LOG(R"( }%{public}s)", trailingComma);
1096 }
1097 LOCAL_LOG(R"(]})");
1098#undef LOCAL_LOG
1099#undef LOCAL_LOG_IF_ALLOWED
1100}
1101
1102void NetworkResourceLoader::logCookieInformation(NetworkConnectionToWebProcess& connection, const String& label, const void* loggedObject, const NetworkStorageSession& networkStorageSession, const URL& firstParty, const SameSiteInfo& sameSiteInfo, const URL& url, const String& referrer, Optional<uint64_t> frameID, Optional<PageIdentifier> pageID, Optional<uint64_t> identifier)
1103{
1104 ASSERT(shouldLogCookieInformation(connection, networkStorageSession.sessionID()));
1105
1106 if (networkStorageSession.shouldBlockCookies(firstParty, url, frameID, pageID))
1107 logBlockedCookieInformation(connection, label, loggedObject, networkStorageSession, firstParty, sameSiteInfo, url, referrer, frameID, pageID, identifier);
1108 else
1109 logCookieInformationInternal(connection, label, loggedObject, networkStorageSession, firstParty, sameSiteInfo, url, referrer, frameID, pageID, identifier);
1110}
1111#endif
1112
1113void NetworkResourceLoader::addConsoleMessage(MessageSource messageSource, MessageLevel messageLevel, const String& message, unsigned long)
1114{
1115 send(Messages::WebPage::AddConsoleMessage { m_parameters.webFrameID, messageSource, messageLevel, message, identifier() }, m_parameters.webPageID);
1116}
1117
1118void NetworkResourceLoader::sendCSPViolationReport(URL&& reportURL, Ref<FormData>&& report)
1119{
1120 send(Messages::WebPage::SendCSPViolationReport { m_parameters.webFrameID, WTFMove(reportURL), IPC::FormDataReference { WTFMove(report) } }, m_parameters.webPageID);
1121}
1122
1123void NetworkResourceLoader::enqueueSecurityPolicyViolationEvent(WebCore::SecurityPolicyViolationEvent::Init&& eventInit)
1124{
1125 send(Messages::WebPage::EnqueueSecurityPolicyViolationEvent { m_parameters.webFrameID, WTFMove(eventInit) }, m_parameters.webPageID);
1126}
1127
1128void NetworkResourceLoader::logSlowCacheRetrieveIfNeeded(const NetworkCache::Cache::RetrieveInfo& info)
1129{
1130#if RELEASE_LOG_DISABLED
1131 UNUSED_PARAM(info);
1132#else
1133 if (!isAlwaysOnLoggingAllowed())
1134 return;
1135 auto duration = info.completionTime - info.startTime;
1136 if (duration < 1_s)
1137 return;
1138 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Took %.0fms, priority %d", duration.milliseconds(), info.priority);
1139 if (info.wasSpeculativeLoad)
1140 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Was speculative load");
1141 if (!info.storageTimings.startTime)
1142 return;
1143 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Storage retrieve time %.0fms", (info.storageTimings.completionTime - info.storageTimings.startTime).milliseconds());
1144 if (info.storageTimings.dispatchTime) {
1145 auto time = (info.storageTimings.dispatchTime - info.storageTimings.startTime).milliseconds();
1146 auto count = info.storageTimings.dispatchCountAtDispatch - info.storageTimings.dispatchCountAtStart;
1147 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Dispatch delay %.0fms, dispatched %lu resources first", time, count);
1148 }
1149 if (info.storageTimings.recordIOStartTime)
1150 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Record I/O time %.0fms", (info.storageTimings.recordIOEndTime - info.storageTimings.recordIOStartTime).milliseconds());
1151 if (info.storageTimings.blobIOStartTime)
1152 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Blob I/O time %.0fms", (info.storageTimings.blobIOEndTime - info.storageTimings.blobIOStartTime).milliseconds());
1153 if (info.storageTimings.synchronizationInProgressAtDispatch)
1154 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Synchronization was in progress");
1155 if (info.storageTimings.shrinkInProgressAtDispatch)
1156 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Shrink was in progress");
1157 if (info.storageTimings.wasCanceled)
1158 RELEASE_LOG_IF_ALLOWED("logSlowCacheRetrieveIfNeeded: Retrieve was canceled");
1159#endif
1160}
1161
1162bool NetworkResourceLoader::isCrossOriginPrefetch() const
1163{
1164 auto request = originalRequest();
1165 return request.httpHeaderField(HTTPHeaderName::Purpose) == "prefetch" && !m_parameters.sourceOrigin->canRequest(request.url());
1166}
1167
1168} // namespace WebKit
1169
1170#undef RELEASE_LOG_IF_ALLOWED
1171#undef RELEASE_LOG_ERROR_IF_ALLOWED
1172