| 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 | |