1 | /* |
2 | * Copyright (C) 2011 Google 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 AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "CachedRawResource.h" |
28 | |
29 | #include "CachedRawResourceClient.h" |
30 | #include "CachedResourceClientWalker.h" |
31 | #include "CachedResourceLoader.h" |
32 | #include "HTTPHeaderNames.h" |
33 | #include "SharedBuffer.h" |
34 | #include "SubresourceLoader.h" |
35 | #include <wtf/CompletionHandler.h> |
36 | #include <wtf/SetForScope.h> |
37 | #include <wtf/text/StringView.h> |
38 | |
39 | namespace WebCore { |
40 | |
41 | CachedRawResource::CachedRawResource(CachedResourceRequest&& request, Type type, const PAL::SessionID& sessionID, const CookieJar* cookieJar) |
42 | : CachedResource(WTFMove(request), type, sessionID, cookieJar) |
43 | , m_identifier(0) |
44 | , m_allowEncodedDataReplacement(true) |
45 | { |
46 | ASSERT(isMainOrMediaOrIconOrRawResource()); |
47 | } |
48 | |
49 | Optional<SharedBufferDataView> CachedRawResource::calculateIncrementalDataChunk(const SharedBuffer* data) const |
50 | { |
51 | size_t previousDataLength = encodedSize(); |
52 | if (!data || data->size() <= previousDataLength) |
53 | return WTF::nullopt; |
54 | return data->getSomeData(previousDataLength); |
55 | } |
56 | |
57 | void CachedRawResource::updateBuffer(SharedBuffer& data) |
58 | { |
59 | // Skip any updateBuffers triggered from nested runloops. We'll have the complete buffer in finishLoading. |
60 | if (m_inIncrementalDataNotify) |
61 | return; |
62 | |
63 | CachedResourceHandle<CachedRawResource> protectedThis(this); |
64 | ASSERT(dataBufferingPolicy() == DataBufferingPolicy::BufferData); |
65 | m_data = &data; |
66 | |
67 | auto previousDataSize = encodedSize(); |
68 | while (data.size() > previousDataSize) { |
69 | auto incrementalData = data.getSomeData(previousDataSize); |
70 | previousDataSize += incrementalData.size(); |
71 | |
72 | SetForScope<bool> notifyScope(m_inIncrementalDataNotify, true); |
73 | notifyClientsDataWasReceived(incrementalData.data(), incrementalData.size()); |
74 | } |
75 | setEncodedSize(data.size()); |
76 | |
77 | if (dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData) { |
78 | if (m_loader) |
79 | m_loader->setDataBufferingPolicy(DataBufferingPolicy::DoNotBufferData); |
80 | clear(); |
81 | } else |
82 | CachedResource::updateBuffer(data); |
83 | |
84 | if (m_delayedFinishLoading) { |
85 | auto delayedFinishLoading = std::exchange(m_delayedFinishLoading, WTF::nullopt); |
86 | finishLoading(delayedFinishLoading->buffer.get()); |
87 | } |
88 | } |
89 | |
90 | void CachedRawResource::updateData(const char* data, unsigned length) |
91 | { |
92 | ASSERT(dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData); |
93 | notifyClientsDataWasReceived(data, length); |
94 | CachedResource::updateData(data, length); |
95 | } |
96 | |
97 | void CachedRawResource::finishLoading(SharedBuffer* data) |
98 | { |
99 | if (m_inIncrementalDataNotify) { |
100 | // We may get here synchronously from updateBuffer() if the callback there ends up spinning a runloop. |
101 | // In that case delay the call. |
102 | m_delayedFinishLoading = makeOptional(DelayedFinishLoading { data }); |
103 | return; |
104 | }; |
105 | CachedResourceHandle<CachedRawResource> protectedThis(this); |
106 | DataBufferingPolicy dataBufferingPolicy = this->dataBufferingPolicy(); |
107 | if (dataBufferingPolicy == DataBufferingPolicy::BufferData) { |
108 | m_data = data; |
109 | |
110 | if (auto incrementalData = calculateIncrementalDataChunk(data)) { |
111 | setEncodedSize(data->size()); |
112 | notifyClientsDataWasReceived(incrementalData->data(), incrementalData->size()); |
113 | } |
114 | } |
115 | |
116 | #if USE(QUICK_LOOK) |
117 | m_allowEncodedDataReplacement = m_loader && !m_loader->isQuickLookResource(); |
118 | #endif |
119 | |
120 | CachedResource::finishLoading(data); |
121 | if (dataBufferingPolicy == DataBufferingPolicy::BufferData && this->dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData) { |
122 | if (m_loader) |
123 | m_loader->setDataBufferingPolicy(DataBufferingPolicy::DoNotBufferData); |
124 | clear(); |
125 | } |
126 | } |
127 | |
128 | void CachedRawResource::notifyClientsDataWasReceived(const char* data, unsigned length) |
129 | { |
130 | if (!length) |
131 | return; |
132 | |
133 | CachedResourceHandle<CachedRawResource> protectedThis(this); |
134 | CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); |
135 | while (CachedRawResourceClient* c = w.next()) |
136 | c->dataReceived(*this, data, length); |
137 | } |
138 | |
139 | static void iterateRedirects(CachedResourceHandle<CachedRawResource>&& handle, CachedRawResourceClient& client, Vector<std::pair<ResourceRequest, ResourceResponse>>&& redirectsInReverseOrder, CompletionHandler<void(ResourceRequest&&)>&& completionHandler) |
140 | { |
141 | if (!handle->hasClient(client) || redirectsInReverseOrder.isEmpty()) |
142 | return completionHandler({ }); |
143 | auto redirectPair = redirectsInReverseOrder.takeLast(); |
144 | client.redirectReceived(*handle, WTFMove(redirectPair.first), WTFMove(redirectPair.second), [handle = WTFMove(handle), client, redirectsInReverseOrder = WTFMove(redirectsInReverseOrder), completionHandler = WTFMove(completionHandler)] (ResourceRequest&&) mutable { |
145 | // Ignore the new request because we can't do anything with it. |
146 | // We're just replying a redirect chain that has already happened. |
147 | iterateRedirects(WTFMove(handle), client, WTFMove(redirectsInReverseOrder), WTFMove(completionHandler)); |
148 | }); |
149 | } |
150 | |
151 | void CachedRawResource::didAddClient(CachedResourceClient& c) |
152 | { |
153 | CachedRawResourceClient& client = static_cast<CachedRawResourceClient&>(c); |
154 | size_t redirectCount = m_redirectChain.size(); |
155 | Vector<std::pair<ResourceRequest, ResourceResponse>> redirectsInReverseOrder; |
156 | redirectsInReverseOrder.reserveInitialCapacity(redirectCount); |
157 | for (size_t i = 0; i < redirectCount; ++i) { |
158 | const auto& pair = m_redirectChain[redirectCount - i - 1]; |
159 | redirectsInReverseOrder.uncheckedAppend(std::make_pair(pair.m_request, pair.m_redirectResponse)); |
160 | } |
161 | iterateRedirects(CachedResourceHandle<CachedRawResource>(this), client, WTFMove(redirectsInReverseOrder), [this, protectedThis = CachedResourceHandle<CachedRawResource>(this), client = &client] (ResourceRequest&&) mutable { |
162 | if (!hasClient(*client)) |
163 | return; |
164 | auto responseProcessedHandler = [this, protectedThis = WTFMove(protectedThis), client] { |
165 | if (!hasClient(*client)) |
166 | return; |
167 | if (m_data) |
168 | client->dataReceived(*this, m_data->data(), m_data->size()); |
169 | if (!hasClient(*client)) |
170 | return; |
171 | CachedResource::didAddClient(*client); |
172 | }; |
173 | |
174 | if (!m_response.isNull()) { |
175 | ResourceResponse response(m_response); |
176 | if (validationCompleting()) |
177 | response.setSource(ResourceResponse::Source::MemoryCacheAfterValidation); |
178 | else { |
179 | ASSERT(!validationInProgress()); |
180 | response.setSource(ResourceResponse::Source::MemoryCache); |
181 | } |
182 | client->responseReceived(*this, response, WTFMove(responseProcessedHandler)); |
183 | } else |
184 | responseProcessedHandler(); |
185 | }); |
186 | } |
187 | |
188 | void CachedRawResource::allClientsRemoved() |
189 | { |
190 | if (m_loader) |
191 | m_loader->cancelIfNotFinishing(); |
192 | } |
193 | |
194 | static void iterateClients(CachedResourceClientWalker<CachedRawResourceClient>&& walker, CachedResourceHandle<CachedRawResource>&& handle, ResourceRequest&& request, std::unique_ptr<ResourceResponse>&& response, CompletionHandler<void(ResourceRequest&&)>&& completionHandler) |
195 | { |
196 | auto client = walker.next(); |
197 | if (!client) |
198 | return completionHandler(WTFMove(request)); |
199 | const ResourceResponse& responseReference = *response; |
200 | client->redirectReceived(*handle, WTFMove(request), responseReference, [walker = WTFMove(walker), handle = WTFMove(handle), response = WTFMove(response), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request) mutable { |
201 | iterateClients(WTFMove(walker), WTFMove(handle), WTFMove(request), WTFMove(response), WTFMove(completionHandler)); |
202 | }); |
203 | } |
204 | |
205 | void CachedRawResource::redirectReceived(ResourceRequest&& request, const ResourceResponse& response, CompletionHandler<void(ResourceRequest&&)>&& completionHandler) |
206 | { |
207 | if (response.isNull()) |
208 | CachedResource::redirectReceived(WTFMove(request), response, WTFMove(completionHandler)); |
209 | else { |
210 | m_redirectChain.append(RedirectPair(request, response)); |
211 | iterateClients(CachedResourceClientWalker<CachedRawResourceClient>(m_clients), CachedResourceHandle<CachedRawResource>(this), WTFMove(request), std::make_unique<ResourceResponse>(response), [this, protectedThis = CachedResourceHandle<CachedRawResource>(this), completionHandler = WTFMove(completionHandler), response] (ResourceRequest&& request) mutable { |
212 | CachedResource::redirectReceived(WTFMove(request), response, WTFMove(completionHandler)); |
213 | }); |
214 | } |
215 | } |
216 | |
217 | void CachedRawResource::responseReceived(const ResourceResponse& response) |
218 | { |
219 | CachedResourceHandle<CachedRawResource> protectedThis(this); |
220 | if (!m_identifier) |
221 | m_identifier = m_loader->identifier(); |
222 | CachedResource::responseReceived(response); |
223 | CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); |
224 | while (CachedRawResourceClient* c = w.next()) |
225 | c->responseReceived(*this, m_response, nullptr); |
226 | } |
227 | |
228 | bool CachedRawResource::shouldCacheResponse(const ResourceResponse& response) |
229 | { |
230 | CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); |
231 | while (CachedRawResourceClient* c = w.next()) { |
232 | if (!c->shouldCacheResponse(*this, response)) |
233 | return false; |
234 | } |
235 | return true; |
236 | } |
237 | |
238 | void CachedRawResource::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) |
239 | { |
240 | CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); |
241 | while (CachedRawResourceClient* c = w.next()) |
242 | c->dataSent(*this, bytesSent, totalBytesToBeSent); |
243 | } |
244 | |
245 | void CachedRawResource::finishedTimingForWorkerLoad(ResourceTiming&& resourceTiming) |
246 | { |
247 | CachedResourceClientWalker<CachedRawResourceClient> w(m_clients); |
248 | while (CachedRawResourceClient* c = w.next()) |
249 | c->finishedTimingForWorkerLoad(*this, resourceTiming); |
250 | } |
251 | |
252 | void CachedRawResource::switchClientsToRevalidatedResource() |
253 | { |
254 | ASSERT(m_loader); |
255 | // If we're in the middle of a successful revalidation, responseReceived() hasn't been called, so we haven't set m_identifier. |
256 | ASSERT(!m_identifier); |
257 | downcast<CachedRawResource>(*resourceToRevalidate()).m_identifier = m_loader->identifier(); |
258 | CachedResource::switchClientsToRevalidatedResource(); |
259 | } |
260 | |
261 | void CachedRawResource::setDefersLoading(bool defers) |
262 | { |
263 | if (m_loader) |
264 | m_loader->setDefersLoading(defers); |
265 | } |
266 | |
267 | void CachedRawResource::setDataBufferingPolicy(DataBufferingPolicy dataBufferingPolicy) |
268 | { |
269 | m_options.dataBufferingPolicy = dataBufferingPolicy; |
270 | } |
271 | |
272 | static bool (HTTPHeaderName name) |
273 | { |
274 | switch (name) { |
275 | // FIXME: This list of headers that don't affect cache policy almost certainly isn't complete. |
276 | case HTTPHeaderName::Accept: |
277 | case HTTPHeaderName::CacheControl: |
278 | case HTTPHeaderName::Pragma: |
279 | case HTTPHeaderName::Purpose: |
280 | case HTTPHeaderName::Referer: |
281 | case HTTPHeaderName::UserAgent: |
282 | return true; |
283 | |
284 | default: |
285 | return false; |
286 | } |
287 | } |
288 | |
289 | bool CachedRawResource::canReuse(const ResourceRequest& newRequest) const |
290 | { |
291 | if (dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData) |
292 | return false; |
293 | |
294 | if (m_resourceRequest.httpMethod() != newRequest.httpMethod()) |
295 | return false; |
296 | |
297 | if (m_resourceRequest.httpBody() != newRequest.httpBody()) |
298 | return false; |
299 | |
300 | if (m_resourceRequest.allowCookies() != newRequest.allowCookies()) |
301 | return false; |
302 | |
303 | if (newRequest.isConditional()) |
304 | return false; |
305 | |
306 | // Ensure most headers match the existing headers before continuing. |
307 | // Note that the list of ignored headers includes some headers explicitly related to caching. |
308 | // A more detailed check of caching policy will be performed later, this is simply a list of |
309 | // headers that we might permit to be different and still reuse the existing CachedResource. |
310 | const HTTPHeaderMap& = newRequest.httpHeaderFields(); |
311 | const HTTPHeaderMap& = m_resourceRequest.httpHeaderFields(); |
312 | |
313 | for (const auto& : newHeaders) { |
314 | if (header.keyAsHTTPHeaderName) { |
315 | if (!shouldIgnoreHeaderForCacheReuse(header.keyAsHTTPHeaderName.value()) |
316 | && header.value != oldHeaders.get(header.keyAsHTTPHeaderName.value())) |
317 | return false; |
318 | } else if (header.value != oldHeaders.get(header.key)) |
319 | return false; |
320 | } |
321 | |
322 | // For this second loop, we don't actually need to compare values, checking that the |
323 | // key is contained in newHeaders is sufficient due to the previous loop. |
324 | for (const auto& : oldHeaders) { |
325 | if (header.keyAsHTTPHeaderName) { |
326 | if (!shouldIgnoreHeaderForCacheReuse(header.keyAsHTTPHeaderName.value()) |
327 | && !newHeaders.contains(header.keyAsHTTPHeaderName.value())) |
328 | return false; |
329 | } else if (!newHeaders.contains(header.key)) |
330 | return false; |
331 | } |
332 | |
333 | return true; |
334 | } |
335 | |
336 | void CachedRawResource::clear() |
337 | { |
338 | m_data = nullptr; |
339 | setEncodedSize(0); |
340 | if (m_loader) |
341 | m_loader->clearResourceData(); |
342 | } |
343 | |
344 | } // namespace WebCore |
345 | |