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 | |
65 | namespace WebKit { |
66 | using namespace WebCore; |
67 | |
68 | struct 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 | |
82 | static 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 | |
95 | NetworkResourceLoader::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 | |
122 | NetworkResourceLoader::~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 | |
132 | bool 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 | |
146 | bool 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 | |
159 | bool NetworkResourceLoader::isSynchronous() const |
160 | { |
161 | return !!m_synchronousLoadData; |
162 | } |
163 | |
164 | void 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 | |
209 | void 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 | |
268 | void 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 | |
309 | void 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 | |
328 | void 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 | |
343 | void 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 | |
368 | bool 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 | |
405 | bool 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 | |
431 | void 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 | |
510 | void 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 | |
540 | void 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 | |
575 | void 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 | |
595 | void NetworkResourceLoader::didBlockAuthenticationChallenge() |
596 | { |
597 | send(Messages::WebResourceLoader::DidBlockAuthenticationChallenge()); |
598 | } |
599 | |
600 | Optional<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 | |
622 | void 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 | |
673 | void 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 | |
689 | void 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 | |
706 | ResourceResponse NetworkResourceLoader::sanitizeResponseIfPossible(ResourceResponse&& response, ResourceResponse::SanitizationType type) |
707 | { |
708 | if (m_parameters.shouldRestrictHTTPResponseAccess) |
709 | response.sanitizeHTTPHeaderFields(type); |
710 | |
711 | return WTFMove(response); |
712 | } |
713 | |
714 | void NetworkResourceLoader::restartNetworkLoad(WebCore::ResourceRequest&& newRequest) |
715 | { |
716 | if (m_networkLoad) |
717 | m_networkLoad->cancel(); |
718 | |
719 | startNetworkLoad(WTFMove(newRequest), FirstLoad::No); |
720 | } |
721 | |
722 | void 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 | |
767 | void 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 | |
779 | void NetworkResourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) |
780 | { |
781 | if (!isSynchronous()) |
782 | send(Messages::WebResourceLoader::DidSendData(bytesSent, totalBytesToBeSent)); |
783 | } |
784 | |
785 | void NetworkResourceLoader::startBufferingTimerIfNeeded() |
786 | { |
787 | if (isSynchronous()) |
788 | return; |
789 | if (m_bufferingTimer.isActive()) |
790 | return; |
791 | m_bufferingTimer.startOneShot(m_parameters.maximumBufferingTime); |
792 | } |
793 | |
794 | void 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 | |
808 | void NetworkResourceLoader::sendBuffer(SharedBuffer& buffer, size_t encodedDataLength) |
809 | { |
810 | ASSERT(!isSynchronous()); |
811 | |
812 | send(Messages::WebResourceLoader::DidReceiveData({ buffer }, encodedDataLength)); |
813 | } |
814 | |
815 | void 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 | |
837 | void 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 | |
873 | void 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 | |
899 | void 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 | |
920 | void 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 | |
931 | IPC::Connection* NetworkResourceLoader::messageSenderConnection() const |
932 | { |
933 | return &connectionToWebProcess().connection(); |
934 | } |
935 | |
936 | void 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 | |
952 | void 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 | |
968 | bool NetworkResourceLoader::isAlwaysOnLoggingAllowed() const |
969 | { |
970 | if (m_connection->networkProcess().sessionIsControlledByAutomation(sessionID())) |
971 | return true; |
972 | |
973 | return sessionID().isAlwaysOnLoggingAllowed(); |
974 | } |
975 | |
976 | bool NetworkResourceLoader::() const |
977 | { |
978 | return m_shouldCaptureExtraNetworkLoadMetrics; |
979 | } |
980 | |
981 | #if ENABLE(RESOURCE_LOAD_STATISTICS) && !RELEASE_LOG_DISABLED |
982 | bool 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 | |
989 | static String escapeForJSON(String s) |
990 | { |
991 | return s.replace('\\', "\\\\" ).replace('"', "\\\"" ); |
992 | } |
993 | |
994 | static String escapeIDForJSON(const Optional<uint64_t>& value) |
995 | { |
996 | return value ? String::number(value.value()) : String("None"_s ); |
997 | }; |
998 | |
999 | static String escapeIDForJSON(const Optional<PageIdentifier>& value) |
1000 | { |
1001 | return value ? String::number(value->toUInt64()) : String("None"_s ); |
1002 | }; |
1003 | |
1004 | void 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 | |
1014 | static 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 | |
1041 | static 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 | |
1102 | void 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 | |
1113 | void 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 | |
1118 | void 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 | |
1123 | void NetworkResourceLoader::enqueueSecurityPolicyViolationEvent(WebCore::SecurityPolicyViolationEvent::Init&& eventInit) |
1124 | { |
1125 | send(Messages::WebPage::EnqueueSecurityPolicyViolationEvent { m_parameters.webFrameID, WTFMove(eventInit) }, m_parameters.webPageID); |
1126 | } |
1127 | |
1128 | void 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 | |
1162 | bool 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 | |