113 promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
114}
115
116void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
117{
118 RefPtr<FetchRequest> request;
119 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
120 request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
121 if (request->method() != "GET") {
122 promise.reject(Exception { TypeError, ASCIILiteral("Request method is not GET") });
123 return;
124 }
125 } else {
126 if (UNLIKELY(!scriptExecutionContext()))
127 return;
128 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
129 }
130
131 if (!protocolIsInHTTPFamily(request->url())) {
132 promise.reject(Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") });
133 return;
134 }
135
136 auto varyValue = response->headers().internalHeaders().get(WebCore::HTTPHeaderName::Vary);
137 Vector<String> varyingHeaderNames;
138 bool hasVaryHeaderStar = false;
139 varyValue.split(',', false, [&](const StringView& view) {
140 if (!hasVaryHeaderStar && view == "*")
141 hasVaryHeaderStar = true;
142 });
143
144 if (hasVaryHeaderStar) {
145 promise.reject(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
146 return;
147 }
148
149 if (response->isDisturbed()) {
150 promise.reject(Exception { TypeError, ASCIILiteral("Response is disturbed or locked") });
151 return;
152 }
153
154 if (response->status() == 206) {
155 promise.reject(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
156 return;
157 }
158
159 // FIXME: Add support for being loaded responses.
160 if (response->isLoading()) {
161 promise.reject(Exception { NotSupportedError, ASCIILiteral("Caching a loading Response is not yet supported") });
162 return;
163 }
164
165 // FIXME: Add support for ReadableStream.
166 if (response->isReadableStreamBody()) {
167 promise.reject(Exception { NotSupportedError, ASCIILiteral("Caching a Response with data stored in a ReadableStream is not yet supported") });
168 return;
169 }
170
171 batchPutOperation(*request, response.get(), [promise = WTFMove(promise)](CacheStorageConnection::Error error) mutable {
172 if (error != CacheStorageConnection::Error::None) {
173 promise.reject(CacheStorageConnection::exceptionFromError(error));
174 return;
175 }
176 promise.resolve();
177 });
178}
179
180void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
181{
182 RefPtr<FetchRequest> request;
183 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
184 request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
185 if (request->method() != "GET" && !options.ignoreMethod) {
186 promise.resolve(false);
187 return;
188 }
189 } else {
190 if (UNLIKELY(!scriptExecutionContext()))
191 return;
192 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
193 }
194
195 batchDeleteOperation(*request, WTFMove(options), [promise = WTFMove(promise)](bool didDelete, CacheStorageConnection::Error error) mutable {
196 if (error != CacheStorageConnection::Error::None) {
197 promise.reject(CacheStorageConnection::exceptionFromError(error));
198 return;
199 }
200 promise.resolve(didDelete);
201 });
202}
203
204void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
205{
206 RefPtr<FetchRequest> request;
207 if (info) {
208 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
209 request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
210 if (request->method() != "GET" && !options.ignoreMethod) {
211 promise.resolve(Vector<Ref<FetchRequest>> { });
212 return;
213 }
214 } else {
215 if (UNLIKELY(!scriptExecutionContext()))
216 return;
217 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
218 }
219 }
220
221 if (!request) {
222 refreshRequestToResponseMap([this, promise = WTFMove(promise)]() mutable {
223 Vector<Ref<FetchRequest>> requests;
224 requests.reserveInitialCapacity(m_requestToResponseMap.size());
225 for (auto& record : m_requestToResponseMap)
226 requests.uncheckedAppend(record.request.copyRef());
227 promise.resolve(requests);
228 });
229 return;
230 }
231
232 queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
233 Vector<Ref<FetchRequest>> requests;
234 requests.reserveInitialCapacity(records.size());
235 for (auto& record : records)
236 requests.uncheckedAppend(record.request.copyRef());
237 promise.resolve(requests);
238 });
239}
240
241void Cache::refreshRequestToResponseMap(WTF::Function<void()>&& callback)
242{
243 setPendingActivity(this);
244 m_connection->refreshRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
245 if (!m_isStopped) {
246 updateRequestToResponseMap(WTFMove(records));
247 callback();
248 }
249 unsetPendingActivity(this);
250 });
251}
252
253void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
254{
255 refreshRequestToResponseMap([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
256 callback(queryCacheWithTargetStorage(request.get(), options, m_requestToResponseMap));
257 });
258}
259
260static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
261{
262 // We need to pass the resource request with all correct headers hence why we call resourceRequest().
263 return CacheStorageConnection::queryCacheMatch(request.resourceRequest(), cachedRequest.resourceRequest(), cachedResponse, options);
264}
265
266Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
267{
268 if (!options.ignoreMethod && request.method() != "GET")
269 return { };
270
271 Vector<CacheStorageRecord> records;
272 for (auto& record : targetStorage) {
273 if (queryCacheMatch(request, record.request.get(), record.response->response(), options))
274 records.append({ record.identifier, record.request.copyRef(), record.response.copyRef() });
275 }
276 return records;
277}
278
279void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(bool didRemoval, CacheStorageConnection::Error error)>&& callback)
280{
281 setPendingActivity(this);
282 m_connection->batchDeleteOperation(m_identifier, request.internalRequest().request, WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
283 if (!m_isStopped) {
284 if (error == CacheStorageConnection::Error::None)
285 m_requestToResponseMap.removeAllMatching([&](const auto& item) { return records.contains(item.identifier); });
286
287 callback(!!records.size(), error);
288 }
289 unsetPendingActivity(this);
290 });
291}
292
293static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response)
294{
295 // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
296 ResourceResponse cachedResponse = response.response();
297 for (auto& header : response.headers().internalHeaders())
298 cachedResponse.setHTTPHeaderField(header.key, header.value);
299
300 ResourceRequest cachedRequest = request.internalRequest().request;
301 cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
302
303 return { 0,
304 request.headers().guard(), WTFMove(cachedRequest), request.internalRequest().options, request.internalRequest().referrer,
305 response.headers().guard(), WTFMove(cachedResponse)
306 };
307}
308
309void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, WTF::Function<void(CacheStorageConnection::Error)>&& callback)
310{
311 Vector<CacheStorageConnection::Record> records;
312 records.append(toConnectionRecord(request, response));
313
314 setPendingActivity(this);
315 m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
316 if (!m_isStopped)
317 callback(error);
318
319 unsetPendingActivity(this);
320 });
321}
322
323void Cache::updateRequestToResponseMap(Vector<CacheStorageConnection::Record>&& records)
324{
325 ASSERT(scriptExecutionContext());
326 Vector<CacheStorageRecord> newMap;
327
328 for (auto& record : records) {
329 size_t index = m_requestToResponseMap.findMatching([&](const auto& item) { return item.identifier == record.identifier; });
330 if (index != notFound)
331 newMap.append(WTFMove(m_requestToResponseMap[index]));
332 else {
333 auto requestHeaders = FetchHeaders::create(record.requestHeadersGuard, HTTPHeaderMap { record.request.httpHeaderFields() });
334 FetchRequest::InternalRequest internalRequest = { WTFMove(record.request), WTFMove(record.options), WTFMove(record.referrer) };
335 auto request = FetchRequest::create(*scriptExecutionContext(), std::nullopt, WTFMove(requestHeaders), WTFMove(internalRequest));
336
337 auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
338 auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
339
340 newMap.append(CacheStorageRecord { record.identifier, WTFMove(request), WTFMove(response) });
341 }
342 }
343 m_requestToResponseMap = WTFMove(newMap);