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