1 | /* |
2 | * Copyright (C) 2016 Canon Inc. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted, provided that the following conditions |
6 | * are required to be met: |
7 | * |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * 3. Neither the name of Canon Inc. nor the names of |
14 | * its contributors may be used to endorse or promote products derived |
15 | * from this software without specific prior written permission. |
16 | * |
17 | * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY |
18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
20 | * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR |
21 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
23 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
24 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
25 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "FetchResponse.h" |
31 | |
32 | #include "FetchRequest.h" |
33 | #include "HTTPParsers.h" |
34 | #include "InspectorInstrumentation.h" |
35 | #include "JSBlob.h" |
36 | #include "MIMETypeRegistry.h" |
37 | #include "ReadableStreamSink.h" |
38 | #include "ResourceError.h" |
39 | #include "ScriptExecutionContext.h" |
40 | #include <wtf/text/StringConcatenateNumbers.h> |
41 | |
42 | namespace WebCore { |
43 | |
44 | // https://fetch.spec.whatwg.org/#null-body-status |
45 | static inline bool isNullBodyStatus(int status) |
46 | { |
47 | return status == 101 || status == 204 || status == 205 || status == 304; |
48 | } |
49 | |
50 | Ref<FetchResponse> FetchResponse::create(ScriptExecutionContext& context, Optional<FetchBody>&& body, FetchHeaders::Guard guard, ResourceResponse&& response) |
51 | { |
52 | bool isSynthetic = response.type() == ResourceResponse::Type::Default || response.type() == ResourceResponse::Type::Error; |
53 | bool isOpaque = response.tainting() == ResourceResponse::Tainting::Opaque; |
54 | auto = isOpaque ? FetchHeaders::create(guard) : FetchHeaders::create(guard, HTTPHeaderMap { response.httpHeaderFields() }); |
55 | |
56 | auto fetchResponse = adoptRef(*new FetchResponse(context, WTFMove(body), WTFMove(headers), WTFMove(response))); |
57 | if (!isSynthetic) |
58 | fetchResponse->m_filteredResponse = ResourceResponseBase::filter(fetchResponse->m_internalResponse); |
59 | if (isOpaque) |
60 | fetchResponse->setBodyAsOpaque(); |
61 | return fetchResponse; |
62 | } |
63 | |
64 | ExceptionOr<Ref<FetchResponse>> FetchResponse::create(ScriptExecutionContext& context, Optional<FetchBody::Init>&& body, Init&& init) |
65 | { |
66 | // 1. If init’s status member is not in the range 200 to 599, inclusive, then throw a RangeError. |
67 | if (init.status < 200 || init.status > 599) |
68 | return Exception { RangeError, "Status must be between 200 and 599"_s }; |
69 | |
70 | // 2. If init’s statusText member does not match the reason-phrase token production, then throw a TypeError. |
71 | if (!isValidReasonPhrase(init.statusText)) |
72 | return Exception { TypeError, "Status text must be a valid reason-phrase."_s }; |
73 | |
74 | // 3. Let r be a new Response object associated with a new response. |
75 | // NOTE: Creation of the Response object is delayed until all potential exceptional cases are handled. |
76 | |
77 | // 4. Set r’s headers to a new Headers object, whose header list is r’s response’s header list, and guard is "response". |
78 | auto = FetchHeaders::create(FetchHeaders::Guard::Response); |
79 | |
80 | // 5. Set r’s response’s status to init’s status member. |
81 | auto status = init.status; |
82 | |
83 | // 6. Set r’s response’s status message to init’s statusText member. |
84 | auto statusText = init.statusText; |
85 | |
86 | // 7. If init’s headers member is present, then fill r’s headers with init’s headers member. |
87 | if (init.headers) { |
88 | auto result = headers->fill(*init.headers); |
89 | if (result.hasException()) |
90 | return result.releaseException(); |
91 | } |
92 | |
93 | Optional<FetchBody> extractedBody; |
94 | |
95 | // 8. If body is non-null, run these substeps: |
96 | if (body) { |
97 | // 8.1 If init’s status member is a null body status, then throw a TypeError. |
98 | // (NOTE: 101 is included in null body status due to its use elsewhere. It does not affect this step.) |
99 | if (isNullBodyStatus(init.status)) |
100 | return Exception { TypeError, "Response cannot have a body with the given status."_s }; |
101 | |
102 | // 8.2 Let Content-Type be null. |
103 | String contentType; |
104 | |
105 | // 8.3 Set r’s response’s body and Content-Type to the result of extracting body. |
106 | extractedBody = FetchBody::extract(context, WTFMove(*body), contentType); |
107 | |
108 | // 8.4 If Content-Type is non-null and r’s response’s header list does not contain `Content-Type`, then append |
109 | // `Content-Type`/Content-Type to r’s response’s header list. |
110 | if (!contentType.isNull() && !headers->fastHas(HTTPHeaderName::ContentType)) |
111 | headers->fastSet(HTTPHeaderName::ContentType, contentType); |
112 | } |
113 | |
114 | // 9. Set r’s MIME type to the result of extracting a MIME type from r’s response’s header list. |
115 | auto contentType = headers->fastGet(HTTPHeaderName::ContentType); |
116 | |
117 | // 10. Set r’s response’s HTTPS state to current settings object’s HTTPS state. |
118 | // FIXME: Implement. |
119 | |
120 | // 11. Resolve r’s trailer promise with a new Headers object whose guard is "immutable". |
121 | // FIXME: Implement. |
122 | |
123 | // 12. Return r. |
124 | auto r = adoptRef(*new FetchResponse(context, WTFMove(extractedBody), WTFMove(headers), { })); |
125 | |
126 | r->m_contentType = contentType; |
127 | auto mimeType = extractMIMETypeFromMediaType(contentType); |
128 | r->m_internalResponse.setMimeType(mimeType.isEmpty() ? defaultMIMEType() : mimeType); |
129 | r->m_internalResponse.setTextEncodingName(extractCharsetFromMediaType(contentType)); |
130 | |
131 | r->m_internalResponse.setHTTPStatusCode(status); |
132 | r->m_internalResponse.setHTTPStatusText(statusText); |
133 | |
134 | return r; |
135 | } |
136 | |
137 | Ref<FetchResponse> FetchResponse::error(ScriptExecutionContext& context) |
138 | { |
139 | auto response = adoptRef(*new FetchResponse(context, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { })); |
140 | response->m_internalResponse.setType(Type::Error); |
141 | return response; |
142 | } |
143 | |
144 | ExceptionOr<Ref<FetchResponse>> FetchResponse::redirect(ScriptExecutionContext& context, const String& url, int status) |
145 | { |
146 | // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser. |
147 | URL requestURL = context.completeURL(url); |
148 | if (!requestURL.isValid()) |
149 | return Exception { TypeError, makeString("Redirection URL '" , requestURL.string(), "' is invalid" ) }; |
150 | if (!requestURL.user().isEmpty() || !requestURL.pass().isEmpty()) |
151 | return Exception { TypeError, "Redirection URL contains credentials"_s }; |
152 | if (!ResourceResponse::isRedirectionStatusCode(status)) |
153 | return Exception { RangeError, makeString("Status code " , status, "is not a redirection status code" ) }; |
154 | auto redirectResponse = adoptRef(*new FetchResponse(context, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { })); |
155 | redirectResponse->m_internalResponse.setHTTPStatusCode(status); |
156 | redirectResponse->m_internalResponse.setHTTPHeaderField(HTTPHeaderName::Location, requestURL.string()); |
157 | redirectResponse->m_headers->fastSet(HTTPHeaderName::Location, requestURL.string()); |
158 | return redirectResponse; |
159 | } |
160 | |
161 | FetchResponse::FetchResponse(ScriptExecutionContext& context, Optional<FetchBody>&& body, Ref<FetchHeaders>&& , ResourceResponse&& response) |
162 | : FetchBodyOwner(context, WTFMove(body), WTFMove(headers)) |
163 | , m_internalResponse(WTFMove(response)) |
164 | { |
165 | } |
166 | |
167 | ExceptionOr<Ref<FetchResponse>> FetchResponse::clone(ScriptExecutionContext& context) |
168 | { |
169 | if (isDisturbedOrLocked()) |
170 | return Exception { TypeError, "Body is disturbed or locked"_s }; |
171 | |
172 | ASSERT(scriptExecutionContext()); |
173 | |
174 | // If loading, let's create a stream so that data is teed on both clones. |
175 | if (isLoading() && !m_readableStreamSource) |
176 | createReadableStream(*context.execState()); |
177 | |
178 | // Synthetic responses do not store headers in m_internalResponse. |
179 | if (m_internalResponse.type() == ResourceResponse::Type::Default) |
180 | m_internalResponse.setHTTPHeaderFields(HTTPHeaderMap { headers().internalHeaders() }); |
181 | |
182 | auto clone = FetchResponse::create(context, WTF::nullopt, headers().guard(), ResourceResponse { m_internalResponse }); |
183 | clone->cloneBody(*this); |
184 | clone->m_opaqueLoadIdentifier = m_opaqueLoadIdentifier; |
185 | clone->m_bodySizeWithPadding = m_bodySizeWithPadding; |
186 | return clone; |
187 | } |
188 | |
189 | void FetchResponse::addAbortSteps(Ref<AbortSignal>&& signal) |
190 | { |
191 | m_abortSignal = WTFMove(signal); |
192 | m_abortSignal->addAlgorithm([this, weakThis = makeWeakPtr(this)] { |
193 | // FIXME: Cancel request body if it is a stream. |
194 | if (!weakThis) |
195 | return; |
196 | |
197 | m_abortSignal = nullptr; |
198 | |
199 | setLoadingError(Exception { AbortError, "Fetch is aborted"_s }); |
200 | |
201 | if (m_bodyLoader) { |
202 | if (auto callback = m_bodyLoader->takeNotificationCallback()) |
203 | callback(Exception { AbortError, "Fetch is aborted"_s }); |
204 | |
205 | if (auto callback = m_bodyLoader->takeConsumeDataCallback()) |
206 | callback(Exception { AbortError, "Fetch is aborted"_s }); |
207 | } |
208 | |
209 | if (m_readableStreamSource) { |
210 | if (!m_readableStreamSource->isCancelling()) |
211 | m_readableStreamSource->error(*loadingException()); |
212 | m_readableStreamSource = nullptr; |
213 | } |
214 | if (m_body) |
215 | m_body->loadingFailed(*loadingException()); |
216 | |
217 | if (auto bodyLoader = WTFMove(m_bodyLoader)) |
218 | bodyLoader->stop(); |
219 | }); |
220 | } |
221 | |
222 | void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& request, NotificationCallback&& responseCallback) |
223 | { |
224 | if (request.signal().aborted()) { |
225 | responseCallback(Exception { AbortError, "Request signal is aborted"_s }); |
226 | // FIXME: Cancel request body if it is a stream. |
227 | return; |
228 | } |
229 | |
230 | if (request.hasReadableStreamBody()) { |
231 | responseCallback(Exception { NotSupportedError, "ReadableStream uploading is not supported"_s }); |
232 | return; |
233 | } |
234 | |
235 | InspectorInstrumentation::willFetch(context, request.url()); |
236 | |
237 | auto response = adoptRef(*new FetchResponse(context, FetchBody { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { })); |
238 | |
239 | response->body().consumer().setAsLoading(); |
240 | |
241 | response->addAbortSteps(request.signal()); |
242 | |
243 | response->m_bodyLoader.emplace(response.get(), WTFMove(responseCallback)); |
244 | if (!response->m_bodyLoader->start(context, request)) |
245 | response->m_bodyLoader = WTF::nullopt; |
246 | } |
247 | |
248 | const String& FetchResponse::url() const |
249 | { |
250 | if (m_responseURL.isNull()) { |
251 | URL url = filteredResponse().url(); |
252 | url.removeFragmentIdentifier(); |
253 | m_responseURL = url.string(); |
254 | } |
255 | return m_responseURL; |
256 | } |
257 | |
258 | const ResourceResponse& FetchResponse::filteredResponse() const |
259 | { |
260 | if (m_filteredResponse) |
261 | return m_filteredResponse.value(); |
262 | return m_internalResponse; |
263 | } |
264 | |
265 | void FetchResponse::BodyLoader::didSucceed() |
266 | { |
267 | ASSERT(m_response.hasPendingActivity()); |
268 | m_response.m_body->loadingSucceeded(); |
269 | |
270 | #if ENABLE(STREAMS_API) |
271 | if (m_response.m_readableStreamSource) { |
272 | if (m_response.body().consumer().hasData()) |
273 | m_response.m_readableStreamSource->enqueue(m_response.body().consumer().takeAsArrayBuffer()); |
274 | |
275 | m_response.closeStream(); |
276 | } |
277 | #endif |
278 | if (auto consumeDataCallback = WTFMove(m_consumeDataCallback)) |
279 | consumeDataCallback(nullptr); |
280 | |
281 | if (m_loader->isStarted()) { |
282 | Ref<FetchResponse> protector(m_response); |
283 | m_response.m_bodyLoader = WTF::nullopt; |
284 | } |
285 | } |
286 | |
287 | void FetchResponse::BodyLoader::didFail(const ResourceError& error) |
288 | { |
289 | ASSERT(m_response.hasPendingActivity()); |
290 | |
291 | m_response.setLoadingError(ResourceError { error }); |
292 | |
293 | if (auto responseCallback = WTFMove(m_responseCallback)) |
294 | responseCallback(Exception { TypeError, error.localizedDescription() }); |
295 | |
296 | if (auto consumeDataCallback = WTFMove(m_consumeDataCallback)) |
297 | consumeDataCallback(Exception { TypeError, error.localizedDescription() }); |
298 | |
299 | #if ENABLE(STREAMS_API) |
300 | if (m_response.m_readableStreamSource) { |
301 | if (!m_response.m_readableStreamSource->isCancelling()) |
302 | m_response.m_readableStreamSource->error(*m_response.loadingException()); |
303 | m_response.m_readableStreamSource = nullptr; |
304 | } |
305 | #endif |
306 | |
307 | // Check whether didFail is called as part of FetchLoader::start. |
308 | if (m_loader && m_loader->isStarted()) { |
309 | Ref<FetchResponse> protector(m_response); |
310 | m_response.m_bodyLoader = WTF::nullopt; |
311 | } |
312 | } |
313 | |
314 | FetchResponse::BodyLoader::BodyLoader(FetchResponse& response, NotificationCallback&& responseCallback) |
315 | : m_response(response) |
316 | , m_responseCallback(WTFMove(responseCallback)) |
317 | , m_pendingActivity(m_response.makePendingActivity(m_response)) |
318 | { |
319 | } |
320 | |
321 | FetchResponse::BodyLoader::~BodyLoader() |
322 | { |
323 | } |
324 | |
325 | static uint64_t nextOpaqueLoadIdentifier { 0 }; |
326 | void FetchResponse::BodyLoader::didReceiveResponse(const ResourceResponse& resourceResponse) |
327 | { |
328 | m_response.m_filteredResponse = ResourceResponseBase::filter(resourceResponse); |
329 | m_response.m_internalResponse = resourceResponse; |
330 | m_response.m_internalResponse.setType(m_response.m_filteredResponse->type()); |
331 | if (resourceResponse.tainting() == ResourceResponse::Tainting::Opaque) { |
332 | m_response.m_opaqueLoadIdentifier = ++nextOpaqueLoadIdentifier; |
333 | m_response.setBodyAsOpaque(); |
334 | } |
335 | |
336 | m_response.m_headers->filterAndFill(m_response.m_filteredResponse->httpHeaderFields(), FetchHeaders::Guard::Response); |
337 | m_response.updateContentType(); |
338 | |
339 | if (auto responseCallback = WTFMove(m_responseCallback)) |
340 | responseCallback(m_response); |
341 | } |
342 | |
343 | void FetchResponse::BodyLoader::didReceiveData(const char* data, size_t size) |
344 | { |
345 | #if ENABLE(STREAMS_API) |
346 | ASSERT(m_response.m_readableStreamSource || m_consumeDataCallback); |
347 | #else |
348 | ASSERT(m_consumeDataCallback); |
349 | #endif |
350 | |
351 | if (m_consumeDataCallback) { |
352 | ReadableStreamChunk chunk { reinterpret_cast<const uint8_t*>(data), size }; |
353 | m_consumeDataCallback(&chunk); |
354 | return; |
355 | } |
356 | |
357 | #if ENABLE(STREAMS_API) |
358 | auto& source = *m_response.m_readableStreamSource; |
359 | |
360 | if (!source.isPulling()) { |
361 | m_response.body().consumer().append(data, size); |
362 | return; |
363 | } |
364 | |
365 | if (m_response.body().consumer().hasData() && !source.enqueue(m_response.body().consumer().takeAsArrayBuffer())) { |
366 | stop(); |
367 | return; |
368 | } |
369 | if (!source.enqueue(ArrayBuffer::tryCreate(data, size))) { |
370 | stop(); |
371 | return; |
372 | } |
373 | source.resolvePullPromise(); |
374 | #else |
375 | UNUSED_PARAM(data); |
376 | UNUSED_PARAM(size); |
377 | #endif |
378 | } |
379 | |
380 | bool FetchResponse::BodyLoader::start(ScriptExecutionContext& context, const FetchRequest& request) |
381 | { |
382 | m_loader = std::make_unique<FetchLoader>(*this, &m_response.m_body->consumer()); |
383 | m_loader->start(context, request); |
384 | return m_loader->isStarted(); |
385 | } |
386 | |
387 | void FetchResponse::BodyLoader::stop() |
388 | { |
389 | m_responseCallback = { }; |
390 | if (m_loader) |
391 | m_loader->stop(); |
392 | } |
393 | |
394 | void FetchResponse::BodyLoader::consumeDataByChunk(ConsumeDataByChunkCallback&& consumeDataCallback) |
395 | { |
396 | ASSERT(!m_consumeDataCallback); |
397 | m_consumeDataCallback = WTFMove(consumeDataCallback); |
398 | auto data = m_loader->startStreaming(); |
399 | if (!data) |
400 | return; |
401 | |
402 | ReadableStreamChunk chunk { reinterpret_cast<const uint8_t*>(data->data()), data->size() }; |
403 | m_consumeDataCallback(&chunk); |
404 | } |
405 | |
406 | FetchResponse::ResponseData FetchResponse::consumeBody() |
407 | { |
408 | ASSERT(!isBodyReceivedByChunk()); |
409 | |
410 | if (isBodyNull()) |
411 | return nullptr; |
412 | |
413 | ASSERT(!m_isDisturbed); |
414 | m_isDisturbed = true; |
415 | |
416 | return body().take(); |
417 | } |
418 | |
419 | void FetchResponse::consumeBodyReceivedByChunk(ConsumeDataByChunkCallback&& callback) |
420 | { |
421 | ASSERT(isBodyReceivedByChunk()); |
422 | ASSERT(!isDisturbed()); |
423 | m_isDisturbed = true; |
424 | |
425 | if (hasReadableStreamBody()) { |
426 | m_body->consumer().extract(*m_body->readableStream(), WTFMove(callback)); |
427 | return; |
428 | } |
429 | |
430 | ASSERT(isLoading()); |
431 | m_bodyLoader->consumeDataByChunk(WTFMove(callback)); |
432 | } |
433 | |
434 | void FetchResponse::setBodyData(ResponseData&& data, uint64_t bodySizeWithPadding) |
435 | { |
436 | m_bodySizeWithPadding = bodySizeWithPadding; |
437 | WTF::switchOn(data, |
438 | [this](Ref<FormData>& formData) { |
439 | if (isBodyNull()) |
440 | setBody({ }); |
441 | body().setAsFormData(WTFMove(formData)); |
442 | }, |
443 | [this](Ref<SharedBuffer>& buffer) { |
444 | if (isBodyNull()) |
445 | setBody({ }); |
446 | body().consumer().setData(WTFMove(buffer)); |
447 | }, |
448 | [](std::nullptr_t&) { |
449 | } |
450 | ); |
451 | } |
452 | |
453 | #if ENABLE(STREAMS_API) |
454 | void FetchResponse::consumeChunk(Ref<JSC::Uint8Array>&& chunk) |
455 | { |
456 | body().consumer().append(chunk->data(), chunk->byteLength()); |
457 | } |
458 | |
459 | void FetchResponse::consumeBodyAsStream() |
460 | { |
461 | ASSERT(m_readableStreamSource); |
462 | if (!isLoading()) { |
463 | FetchBodyOwner::consumeBodyAsStream(); |
464 | return; |
465 | } |
466 | |
467 | ASSERT(m_bodyLoader); |
468 | |
469 | auto data = m_bodyLoader->startStreaming(); |
470 | if (data) { |
471 | if (!m_readableStreamSource->enqueue(data->tryCreateArrayBuffer())) { |
472 | stop(); |
473 | return; |
474 | } |
475 | m_readableStreamSource->resolvePullPromise(); |
476 | } |
477 | } |
478 | |
479 | void FetchResponse::closeStream() |
480 | { |
481 | ASSERT(m_readableStreamSource); |
482 | m_readableStreamSource->close(); |
483 | m_readableStreamSource = nullptr; |
484 | } |
485 | |
486 | void FetchResponse::feedStream() |
487 | { |
488 | ASSERT(m_readableStreamSource); |
489 | bool shouldCloseStream = !m_bodyLoader; |
490 | |
491 | if (body().consumer().hasData()) { |
492 | if (!m_readableStreamSource->enqueue(body().consumer().takeAsArrayBuffer())) { |
493 | stop(); |
494 | return; |
495 | } |
496 | if (!shouldCloseStream) { |
497 | m_readableStreamSource->resolvePullPromise(); |
498 | return; |
499 | } |
500 | } else if (!shouldCloseStream) |
501 | return; |
502 | |
503 | closeStream(); |
504 | } |
505 | |
506 | RefPtr<SharedBuffer> FetchResponse::BodyLoader::startStreaming() |
507 | { |
508 | ASSERT(m_loader); |
509 | return m_loader->startStreaming(); |
510 | } |
511 | |
512 | void FetchResponse::cancel() |
513 | { |
514 | m_isDisturbed = true; |
515 | stop(); |
516 | } |
517 | |
518 | #endif |
519 | |
520 | void FetchResponse::stop() |
521 | { |
522 | RefPtr<FetchResponse> protectedThis(this); |
523 | FetchBodyOwner::stop(); |
524 | if (auto bodyLoader = WTFMove(m_bodyLoader)) |
525 | bodyLoader->stop(); |
526 | } |
527 | |
528 | const char* FetchResponse::activeDOMObjectName() const |
529 | { |
530 | return "Response" ; |
531 | } |
532 | |
533 | bool FetchResponse::canSuspendForDocumentSuspension() const |
534 | { |
535 | // FIXME: We can probably do the same strategy as XHR. |
536 | return !isActive(); |
537 | } |
538 | |
539 | ResourceResponse FetchResponse::resourceResponse() const |
540 | { |
541 | auto response = m_internalResponse; |
542 | |
543 | if (headers().guard() != FetchHeaders::Guard::Immutable) { |
544 | // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase. |
545 | for (auto& : headers().internalHeaders()) |
546 | response.setHTTPHeaderField(header.key, header.value); |
547 | } |
548 | |
549 | return response; |
550 | } |
551 | |
552 | } // namespace WebCore |
553 | |