1/*
2 * Copyright (C) 2006, 2008, 2016 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "ResourceResponseBase.h"
29
30#include "CacheValidation.h"
31#include "HTTPHeaderNames.h"
32#include "HTTPParsers.h"
33#include "MIMETypeRegistry.h"
34#include "ParsedContentRange.h"
35#include "ResourceResponse.h"
36#include <wtf/MathExtras.h>
37#include <wtf/StdLibExtras.h>
38#include <wtf/text/StringView.h>
39
40namespace WebCore {
41
42bool isScriptAllowedByNosniff(const ResourceResponse& response)
43{
44 if (parseContentTypeOptionsHeader(response.httpHeaderField(HTTPHeaderName::XContentTypeOptions)) != ContentTypeOptionsNosniff)
45 return true;
46 String mimeType = extractMIMETypeFromMediaType(response.httpHeaderField(HTTPHeaderName::ContentType));
47 return MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType);
48}
49
50ResourceResponseBase::ResourceResponseBase()
51 : m_haveParsedCacheControlHeader(false)
52 , m_haveParsedAgeHeader(false)
53 , m_haveParsedDateHeader(false)
54 , m_haveParsedExpiresHeader(false)
55 , m_haveParsedLastModifiedHeader(false)
56 , m_haveParsedContentRangeHeader(false)
57 , m_isRedirected(false)
58 , m_isNull(true)
59{
60}
61
62ResourceResponseBase::ResourceResponseBase(const URL& url, const String& mimeType, long long expectedLength, const String& textEncodingName)
63 : m_url(url)
64 , m_mimeType(mimeType)
65 , m_expectedContentLength(expectedLength)
66 , m_textEncodingName(textEncodingName)
67 , m_certificateInfo(CertificateInfo()) // Empty but valid for synthetic responses.
68 , m_haveParsedCacheControlHeader(false)
69 , m_haveParsedAgeHeader(false)
70 , m_haveParsedDateHeader(false)
71 , m_haveParsedExpiresHeader(false)
72 , m_haveParsedLastModifiedHeader(false)
73 , m_haveParsedContentRangeHeader(false)
74 , m_isRedirected(false)
75 , m_isNull(false)
76{
77}
78
79ResourceResponseBase::CrossThreadData ResourceResponseBase::crossThreadData() const
80{
81 CrossThreadData data;
82
83 data.url = url().isolatedCopy();
84 data.mimeType = mimeType().isolatedCopy();
85 data.expectedContentLength = expectedContentLength();
86 data.textEncodingName = textEncodingName().isolatedCopy();
87
88 data.httpStatusCode = httpStatusCode();
89 data.httpStatusText = httpStatusText().isolatedCopy();
90 data.httpVersion = httpVersion().isolatedCopy();
91
92 data.httpHeaderFields = httpHeaderFields().isolatedCopy();
93 data.networkLoadMetrics = m_networkLoadMetrics.isolatedCopy();
94 data.type = m_type;
95 data.tainting = m_tainting;
96 data.isRedirected = m_isRedirected;
97
98 return data;
99}
100
101ResourceResponse ResourceResponseBase::fromCrossThreadData(CrossThreadData&& data)
102{
103 ResourceResponse response;
104
105 response.setURL(data.url);
106 response.setMimeType(data.mimeType);
107 response.setExpectedContentLength(data.expectedContentLength);
108 response.setTextEncodingName(data.textEncodingName);
109
110 response.setHTTPStatusCode(data.httpStatusCode);
111 response.setHTTPStatusText(data.httpStatusText);
112 response.setHTTPVersion(data.httpVersion);
113
114 response.m_httpHeaderFields = WTFMove(data.httpHeaderFields);
115 response.m_networkLoadMetrics = data.networkLoadMetrics;
116 response.m_type = data.type;
117 response.m_tainting = data.tainting;
118 response.m_isRedirected = data.isRedirected;
119
120 return response;
121}
122
123ResourceResponse ResourceResponseBase::syntheticRedirectResponse(const URL& fromURL, const URL& toURL)
124{
125 ResourceResponse redirectResponse;
126 redirectResponse.setURL(fromURL);
127 redirectResponse.setHTTPStatusCode(302);
128 redirectResponse.setHTTPVersion("HTTP/1.1"_s);
129 redirectResponse.setHTTPHeaderField(HTTPHeaderName::Location, toURL.string());
130 redirectResponse.setHTTPHeaderField(HTTPHeaderName::CacheControl, "no-store"_s);
131
132 return redirectResponse;
133}
134
135ResourceResponse ResourceResponseBase::filter(const ResourceResponse& response)
136{
137 if (response.tainting() == Tainting::Opaque) {
138 ResourceResponse opaqueResponse;
139 opaqueResponse.setTainting(Tainting::Opaque);
140 opaqueResponse.setType(Type::Opaque);
141 return opaqueResponse;
142 }
143
144 if (response.tainting() == Tainting::Opaqueredirect) {
145 ResourceResponse opaqueResponse;
146 opaqueResponse.setTainting(Tainting::Opaqueredirect);
147 opaqueResponse.setType(Type::Opaqueredirect);
148 opaqueResponse.setURL(response.url());
149 return opaqueResponse;
150 }
151
152 ResourceResponse filteredResponse = response;
153 // Let's initialize filteredResponse to remove some header fields.
154 filteredResponse.lazyInit(AllFields);
155
156 if (response.tainting() == Tainting::Basic) {
157 filteredResponse.setType(Type::Basic);
158 filteredResponse.m_httpHeaderFields.remove(HTTPHeaderName::SetCookie);
159 filteredResponse.m_httpHeaderFields.remove(HTTPHeaderName::SetCookie2);
160 return filteredResponse;
161 }
162
163 ASSERT(response.tainting() == Tainting::Cors);
164 filteredResponse.setType(Type::Cors);
165
166 auto accessControlExposeHeaderSet = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders));
167 filteredResponse.m_httpHeaderFields.uncommonHeaders().removeAllMatching([&](auto& entry) {
168 return !isCrossOriginSafeHeader(entry.key, accessControlExposeHeaderSet);
169 });
170 filteredResponse.m_httpHeaderFields.commonHeaders().removeAllMatching([&](auto& entry) {
171 return !isCrossOriginSafeHeader(entry.key, accessControlExposeHeaderSet);
172 });
173
174 return filteredResponse;
175}
176
177// FIXME: Name does not make it clear this is true for HTTPS!
178bool ResourceResponseBase::isHTTP() const
179{
180 lazyInit(CommonFieldsOnly);
181
182 return m_url.protocolIsInHTTPFamily();
183}
184
185const URL& ResourceResponseBase::url() const
186{
187 lazyInit(CommonFieldsOnly);
188
189 return m_url;
190}
191
192void ResourceResponseBase::setURL(const URL& url)
193{
194 lazyInit(CommonFieldsOnly);
195 m_isNull = false;
196
197 m_url = url;
198
199 // FIXME: Should invalidate or update platform response if present.
200}
201
202const String& ResourceResponseBase::mimeType() const
203{
204 lazyInit(CommonFieldsOnly);
205
206 return m_mimeType;
207}
208
209void ResourceResponseBase::setMimeType(const String& mimeType)
210{
211 lazyInit(CommonFieldsOnly);
212 m_isNull = false;
213
214 // FIXME: MIME type is determined by HTTP Content-Type header. We should update the header, so that it doesn't disagree with m_mimeType.
215 m_mimeType = mimeType;
216
217 // FIXME: Should invalidate or update platform response if present.
218}
219
220long long ResourceResponseBase::expectedContentLength() const
221{
222 lazyInit(CommonFieldsOnly);
223
224 return m_expectedContentLength;
225}
226
227void ResourceResponseBase::setExpectedContentLength(long long expectedContentLength)
228{
229 lazyInit(CommonFieldsOnly);
230 m_isNull = false;
231
232 // FIXME: Content length is determined by HTTP Content-Length header. We should update the header, so that it doesn't disagree with m_expectedContentLength.
233 m_expectedContentLength = expectedContentLength;
234
235 // FIXME: Should invalidate or update platform response if present.
236}
237
238const String& ResourceResponseBase::textEncodingName() const
239{
240 lazyInit(CommonFieldsOnly);
241
242 return m_textEncodingName;
243}
244
245void ResourceResponseBase::setTextEncodingName(const String& encodingName)
246{
247 lazyInit(CommonFieldsOnly);
248 m_isNull = false;
249
250 // FIXME: Text encoding is determined by HTTP Content-Type header. We should update the header, so that it doesn't disagree with m_textEncodingName.
251 m_textEncodingName = encodingName;
252
253 // FIXME: Should invalidate or update platform response if present.
254}
255
256void ResourceResponseBase::setType(Type type)
257{
258 m_isNull = false;
259 m_type = type;
260}
261
262void ResourceResponseBase::includeCertificateInfo() const
263{
264 if (m_certificateInfo)
265 return;
266 m_certificateInfo = static_cast<const ResourceResponse*>(this)->platformCertificateInfo();
267}
268
269String ResourceResponseBase::suggestedFilename() const
270{
271 return static_cast<const ResourceResponse*>(this)->platformSuggestedFilename();
272}
273
274String ResourceResponseBase::sanitizeSuggestedFilename(const String& suggestedFilename)
275{
276 if (suggestedFilename.isEmpty())
277 return suggestedFilename;
278
279 ResourceResponse response(URL({ }, "http://example.com/"), String(), -1, String());
280 response.setHTTPStatusCode(200);
281 String escapedSuggestedFilename = String(suggestedFilename).replace('\\', "\\\\").replace('"', "\\\"");
282 String value = makeString("attachment; filename=\"", escapedSuggestedFilename, '"');
283 response.setHTTPHeaderField(HTTPHeaderName::ContentDisposition, value);
284 return response.suggestedFilename();
285}
286
287bool ResourceResponseBase::isSuccessful() const
288{
289 int code = httpStatusCode();
290 return code >= 200 && code < 300;
291}
292
293int ResourceResponseBase::httpStatusCode() const
294{
295 lazyInit(CommonFieldsOnly);
296
297 return m_httpStatusCode;
298}
299
300void ResourceResponseBase::setHTTPStatusCode(int statusCode)
301{
302 lazyInit(CommonFieldsOnly);
303
304 m_httpStatusCode = statusCode;
305 m_isNull = false;
306
307 // FIXME: Should invalidate or update platform response if present.
308}
309
310bool ResourceResponseBase::isRedirection() const
311{
312 return isRedirectionStatusCode(m_httpStatusCode);
313}
314
315const String& ResourceResponseBase::httpStatusText() const
316{
317 lazyInit(AllFields);
318
319 return m_httpStatusText;
320}
321
322void ResourceResponseBase::setHTTPStatusText(const String& statusText)
323{
324 lazyInit(AllFields);
325
326 m_httpStatusText = statusText;
327
328 // FIXME: Should invalidate or update platform response if present.
329}
330
331const String& ResourceResponseBase::httpVersion() const
332{
333 lazyInit(AllFields);
334
335 return m_httpVersion;
336}
337
338void ResourceResponseBase::setHTTPVersion(const String& versionText)
339{
340 lazyInit(AllFields);
341
342 m_httpVersion = versionText;
343
344 // FIXME: Should invalidate or update platform response if present.
345}
346
347static bool isSafeRedirectionResponseHeader(HTTPHeaderName name)
348{
349 // WebCore needs to keep location and cache related headers as it does caching.
350 // We also keep CORS/ReferrerPolicy headers until CORS checks/Referrer computation are done in NetworkProcess.
351 return name == HTTPHeaderName::Location
352 || name == HTTPHeaderName::ReferrerPolicy
353 || name == HTTPHeaderName::CacheControl
354 || name == HTTPHeaderName::Date
355 || name == HTTPHeaderName::Expires
356 || name == HTTPHeaderName::ETag
357 || name == HTTPHeaderName::LastModified
358 || name == HTTPHeaderName::Age
359 || name == HTTPHeaderName::Pragma
360 || name == HTTPHeaderName::ReferrerPolicy
361 || name == HTTPHeaderName::Refresh
362 || name == HTTPHeaderName::Vary
363 || name == HTTPHeaderName::AccessControlAllowCredentials
364 || name == HTTPHeaderName::AccessControlAllowHeaders
365 || name == HTTPHeaderName::AccessControlAllowMethods
366 || name == HTTPHeaderName::AccessControlAllowOrigin
367 || name == HTTPHeaderName::AccessControlExposeHeaders
368 || name == HTTPHeaderName::AccessControlMaxAge
369 || name == HTTPHeaderName::CrossOriginResourcePolicy
370 || name == HTTPHeaderName::TimingAllowOrigin;
371}
372
373static bool isSafeCrossOriginResponseHeader(HTTPHeaderName name)
374{
375 // All known response headers used in WebProcesses.
376 return name == HTTPHeaderName::AcceptRanges
377 || name == HTTPHeaderName::AccessControlAllowCredentials
378 || name == HTTPHeaderName::AccessControlAllowHeaders
379 || name == HTTPHeaderName::AccessControlAllowMethods
380 || name == HTTPHeaderName::AccessControlAllowOrigin
381 || name == HTTPHeaderName::AccessControlExposeHeaders
382 || name == HTTPHeaderName::AccessControlMaxAge
383 || name == HTTPHeaderName::AccessControlRequestHeaders
384 || name == HTTPHeaderName::AccessControlRequestMethod
385 || name == HTTPHeaderName::Age
386 || name == HTTPHeaderName::CacheControl
387 || name == HTTPHeaderName::ContentDisposition
388 || name == HTTPHeaderName::ContentEncoding
389 || name == HTTPHeaderName::ContentLanguage
390 || name == HTTPHeaderName::ContentLength
391 || name == HTTPHeaderName::ContentRange
392 || name == HTTPHeaderName::ContentSecurityPolicy
393 || name == HTTPHeaderName::ContentSecurityPolicyReportOnly
394 || name == HTTPHeaderName::ContentType
395 || name == HTTPHeaderName::CrossOriginResourcePolicy
396 || name == HTTPHeaderName::Date
397 || name == HTTPHeaderName::ETag
398 || name == HTTPHeaderName::Expires
399 || name == HTTPHeaderName::IcyMetaInt
400 || name == HTTPHeaderName::IcyMetadata
401 || name == HTTPHeaderName::LastEventID
402 || name == HTTPHeaderName::LastModified
403 || name == HTTPHeaderName::Link
404 || name == HTTPHeaderName::Location
405 || name == HTTPHeaderName::Pragma
406 || name == HTTPHeaderName::Range
407 || name == HTTPHeaderName::ReferrerPolicy
408 || name == HTTPHeaderName::Refresh
409 || name == HTTPHeaderName::ServerTiming
410 || name == HTTPHeaderName::SourceMap
411 || name == HTTPHeaderName::XSourceMap
412 || name == HTTPHeaderName::TimingAllowOrigin
413 || name == HTTPHeaderName::Trailer
414 || name == HTTPHeaderName::Vary
415 || name == HTTPHeaderName::XContentTypeOptions
416 || name == HTTPHeaderName::XDNSPrefetchControl
417 || name == HTTPHeaderName::XFrameOptions
418 || name == HTTPHeaderName::XWebKitCSP
419 || name == HTTPHeaderName::XWebKitCSPReportOnly
420 || name == HTTPHeaderName::XXSSProtection;
421}
422
423void ResourceResponseBase::sanitizeHTTPHeaderFieldsAccordingToTainting()
424{
425 switch (m_tainting) {
426 case ResourceResponse::Tainting::Basic:
427 return;
428 case ResourceResponse::Tainting::Cors: {
429 HTTPHeaderMap filteredHeaders;
430 for (auto& header : m_httpHeaderFields.commonHeaders()) {
431 if (isSafeCrossOriginResponseHeader(header.key))
432 filteredHeaders.add(header.key, WTFMove(header.value));
433 }
434 auto corsSafeHeaderSet = parseAccessControlAllowList<ASCIICaseInsensitiveHash>(httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders));
435 for (auto& headerName : corsSafeHeaderSet) {
436 if (!filteredHeaders.contains(headerName)) {
437 auto value = m_httpHeaderFields.get(headerName);
438 if (!value.isNull())
439 filteredHeaders.add(headerName, value);
440 }
441 }
442 m_httpHeaderFields = WTFMove(filteredHeaders);
443 return;
444 }
445 case ResourceResponse::Tainting::Opaque:
446 case ResourceResponse::Tainting::Opaqueredirect: {
447 HTTPHeaderMap filteredHeaders;
448 for (auto& header : m_httpHeaderFields.commonHeaders()) {
449 if (isSafeCrossOriginResponseHeader(header.key))
450 filteredHeaders.add(header.key, WTFMove(header.value));
451 }
452 m_httpHeaderFields = WTFMove(filteredHeaders);
453 return;
454 }
455 }
456}
457
458void ResourceResponseBase::sanitizeHTTPHeaderFields(SanitizationType type)
459{
460 lazyInit(AllFields);
461
462 m_httpHeaderFields.remove(HTTPHeaderName::SetCookie);
463 m_httpHeaderFields.remove(HTTPHeaderName::SetCookie2);
464
465 switch (type) {
466 case SanitizationType::RemoveCookies:
467 return;
468 case SanitizationType::Redirection: {
469 auto commonHeaders = WTFMove(m_httpHeaderFields.commonHeaders());
470 for (auto& header : commonHeaders) {
471 if (isSafeRedirectionResponseHeader(header.key))
472 m_httpHeaderFields.add(header.key, WTFMove(header.value));
473 }
474 m_httpHeaderFields.uncommonHeaders().clear();
475 return;
476 }
477 case SanitizationType::CrossOriginSafe:
478 sanitizeHTTPHeaderFieldsAccordingToTainting();
479 }
480}
481
482bool ResourceResponseBase::isHTTP09() const
483{
484 lazyInit(AllFields);
485
486 return m_httpVersion.startsWith("HTTP/0.9");
487}
488
489String ResourceResponseBase::httpHeaderField(const String& name) const
490{
491 lazyInit(CommonFieldsOnly);
492
493 // If we already have the header, just return it instead of consuming memory by grabing all headers.
494 String value = m_httpHeaderFields.get(name);
495 if (!value.isEmpty())
496 return value;
497
498 lazyInit(AllFields);
499
500 return m_httpHeaderFields.get(name);
501}
502
503String ResourceResponseBase::httpHeaderField(HTTPHeaderName name) const
504{
505 lazyInit(CommonFieldsOnly);
506
507 // If we already have the header, just return it instead of consuming memory by grabing all headers.
508 String value = m_httpHeaderFields.get(name);
509 if (!value.isEmpty())
510 return value;
511
512 lazyInit(AllFields);
513
514 return m_httpHeaderFields.get(name);
515}
516
517void ResourceResponseBase::updateHeaderParsedState(HTTPHeaderName name)
518{
519 switch (name) {
520 case HTTPHeaderName::Age:
521 m_haveParsedAgeHeader = false;
522 break;
523
524 case HTTPHeaderName::CacheControl:
525 case HTTPHeaderName::Pragma:
526 m_haveParsedCacheControlHeader = false;
527 break;
528
529 case HTTPHeaderName::Date:
530 m_haveParsedDateHeader = false;
531 break;
532
533 case HTTPHeaderName::Expires:
534 m_haveParsedExpiresHeader = false;
535 break;
536
537 case HTTPHeaderName::LastModified:
538 m_haveParsedLastModifiedHeader = false;
539 break;
540
541 case HTTPHeaderName::ContentRange:
542 m_haveParsedContentRangeHeader = false;
543 break;
544
545 default:
546 break;
547 }
548}
549
550void ResourceResponseBase::setHTTPHeaderField(const String& name, const String& value)
551{
552 lazyInit(AllFields);
553
554 HTTPHeaderName headerName;
555 if (findHTTPHeaderName(name, headerName))
556 updateHeaderParsedState(headerName);
557
558 m_httpHeaderFields.set(name, value);
559
560 // FIXME: Should invalidate or update platform response if present.
561}
562
563void ResourceResponseBase::setHTTPHeaderFields(HTTPHeaderMap&& headerFields)
564{
565 lazyInit(AllFields);
566
567 m_httpHeaderFields = WTFMove(headerFields);
568}
569
570void ResourceResponseBase::setHTTPHeaderField(HTTPHeaderName name, const String& value)
571{
572 lazyInit(AllFields);
573
574 updateHeaderParsedState(name);
575
576 m_httpHeaderFields.set(name, value);
577
578 // FIXME: Should invalidate or update platform response if present.
579}
580
581void ResourceResponseBase::addHTTPHeaderField(HTTPHeaderName name, const String& value)
582{
583 lazyInit(AllFields);
584 updateHeaderParsedState(name);
585 m_httpHeaderFields.add(name, value);
586}
587
588void ResourceResponseBase::addHTTPHeaderField(const String& name, const String& value)
589{
590 HTTPHeaderName headerName;
591 if (findHTTPHeaderName(name, headerName))
592 addHTTPHeaderField(headerName, value);
593 else {
594 lazyInit(AllFields);
595 m_httpHeaderFields.add(name, value);
596 }
597}
598
599const HTTPHeaderMap& ResourceResponseBase::httpHeaderFields() const
600{
601 lazyInit(AllFields);
602
603 return m_httpHeaderFields;
604}
605
606void ResourceResponseBase::parseCacheControlDirectives() const
607{
608 ASSERT(!m_haveParsedCacheControlHeader);
609
610 lazyInit(CommonFieldsOnly);
611
612 m_cacheControlDirectives = WebCore::parseCacheControlDirectives(m_httpHeaderFields);
613 m_haveParsedCacheControlHeader = true;
614}
615
616bool ResourceResponseBase::cacheControlContainsNoCache() const
617{
618 if (!m_haveParsedCacheControlHeader)
619 parseCacheControlDirectives();
620 return m_cacheControlDirectives.noCache;
621}
622
623bool ResourceResponseBase::cacheControlContainsNoStore() const
624{
625 if (!m_haveParsedCacheControlHeader)
626 parseCacheControlDirectives();
627 return m_cacheControlDirectives.noStore;
628}
629
630bool ResourceResponseBase::cacheControlContainsMustRevalidate() const
631{
632 if (!m_haveParsedCacheControlHeader)
633 parseCacheControlDirectives();
634 return m_cacheControlDirectives.mustRevalidate;
635}
636
637bool ResourceResponseBase::cacheControlContainsImmutable() const
638{
639 if (!m_haveParsedCacheControlHeader)
640 parseCacheControlDirectives();
641 return m_cacheControlDirectives.immutable;
642}
643
644bool ResourceResponseBase::hasCacheValidatorFields() const
645{
646 lazyInit(CommonFieldsOnly);
647
648 return !m_httpHeaderFields.get(HTTPHeaderName::LastModified).isEmpty() || !m_httpHeaderFields.get(HTTPHeaderName::ETag).isEmpty();
649}
650
651Optional<Seconds> ResourceResponseBase::cacheControlMaxAge() const
652{
653 if (!m_haveParsedCacheControlHeader)
654 parseCacheControlDirectives();
655 return m_cacheControlDirectives.maxAge;
656}
657
658static Optional<WallTime> parseDateValueInHeader(const HTTPHeaderMap& headers, HTTPHeaderName headerName)
659{
660 String headerValue = headers.get(headerName);
661 if (headerValue.isEmpty())
662 return WTF::nullopt;
663 // This handles all date formats required by RFC2616:
664 // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
665 // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
666 // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
667 return parseHTTPDate(headerValue);
668}
669
670Optional<WallTime> ResourceResponseBase::date() const
671{
672 lazyInit(CommonFieldsOnly);
673
674 if (!m_haveParsedDateHeader) {
675 m_date = parseDateValueInHeader(m_httpHeaderFields, HTTPHeaderName::Date);
676 m_haveParsedDateHeader = true;
677 }
678 return m_date;
679}
680
681Optional<Seconds> ResourceResponseBase::age() const
682{
683 lazyInit(CommonFieldsOnly);
684
685 if (!m_haveParsedAgeHeader) {
686 String headerValue = m_httpHeaderFields.get(HTTPHeaderName::Age);
687 bool ok;
688 double ageDouble = headerValue.toDouble(&ok);
689 if (ok)
690 m_age = Seconds { ageDouble };
691 m_haveParsedAgeHeader = true;
692 }
693 return m_age;
694}
695
696Optional<WallTime> ResourceResponseBase::expires() const
697{
698 lazyInit(CommonFieldsOnly);
699
700 if (!m_haveParsedExpiresHeader) {
701 m_expires = parseDateValueInHeader(m_httpHeaderFields, HTTPHeaderName::Expires);
702 m_haveParsedExpiresHeader = true;
703 }
704 return m_expires;
705}
706
707Optional<WallTime> ResourceResponseBase::lastModified() const
708{
709 lazyInit(CommonFieldsOnly);
710
711 if (!m_haveParsedLastModifiedHeader) {
712 m_lastModified = parseDateValueInHeader(m_httpHeaderFields, HTTPHeaderName::LastModified);
713#if PLATFORM(COCOA)
714 // CFNetwork converts malformed dates into Epoch so we need to treat Epoch as
715 // an invalid value (rdar://problem/22352838).
716 const WallTime epoch = WallTime::fromRawSeconds(0);
717 if (m_lastModified && m_lastModified.value() == epoch)
718 m_lastModified = WTF::nullopt;
719#endif
720 m_haveParsedLastModifiedHeader = true;
721 }
722 return m_lastModified;
723}
724
725static ParsedContentRange parseContentRangeInHeader(const HTTPHeaderMap& headers)
726{
727 String contentRangeValue = headers.get(HTTPHeaderName::ContentRange);
728 if (contentRangeValue.isEmpty())
729 return ParsedContentRange();
730
731 return ParsedContentRange(contentRangeValue);
732}
733
734const ParsedContentRange& ResourceResponseBase::contentRange() const
735{
736 lazyInit(CommonFieldsOnly);
737
738 if (!m_haveParsedContentRangeHeader) {
739 m_contentRange = parseContentRangeInHeader(m_httpHeaderFields);
740 m_haveParsedContentRangeHeader = true;
741 }
742
743 return m_contentRange;
744}
745
746bool ResourceResponseBase::isAttachment() const
747{
748 lazyInit(AllFields);
749
750 auto value = m_httpHeaderFields.get(HTTPHeaderName::ContentDisposition);
751 return equalLettersIgnoringASCIICase(value.left(value.find(';')).stripWhiteSpace(), "attachment");
752}
753
754bool ResourceResponseBase::isAttachmentWithFilename() const
755{
756 lazyInit(AllFields);
757
758 String contentDisposition = m_httpHeaderFields.get(HTTPHeaderName::ContentDisposition);
759 if (contentDisposition.isNull())
760 return false;
761
762 if (!equalLettersIgnoringASCIICase(contentDisposition.left(contentDisposition.find(';')).stripWhiteSpace(), "attachment"))
763 return false;
764
765 String filename = filenameFromHTTPContentDisposition(contentDisposition);
766 return !filename.isNull();
767}
768
769ResourceResponseBase::Source ResourceResponseBase::source() const
770{
771 lazyInit(AllFields);
772
773 return m_source;
774}
775
776void ResourceResponseBase::lazyInit(InitLevel initLevel) const
777{
778 const_cast<ResourceResponse*>(static_cast<const ResourceResponse*>(this))->platformLazyInit(initLevel);
779}
780
781bool ResourceResponseBase::compare(const ResourceResponse& a, const ResourceResponse& b)
782{
783 if (a.isNull() != b.isNull())
784 return false;
785 if (a.url() != b.url())
786 return false;
787 if (a.mimeType() != b.mimeType())
788 return false;
789 if (a.expectedContentLength() != b.expectedContentLength())
790 return false;
791 if (a.textEncodingName() != b.textEncodingName())
792 return false;
793 if (a.suggestedFilename() != b.suggestedFilename())
794 return false;
795 if (a.httpStatusCode() != b.httpStatusCode())
796 return false;
797 if (a.httpStatusText() != b.httpStatusText())
798 return false;
799 if (a.httpHeaderFields() != b.httpHeaderFields())
800 return false;
801 if (a.deprecatedNetworkLoadMetrics() != b.deprecatedNetworkLoadMetrics())
802 return false;
803 return ResourceResponse::platformCompare(a, b);
804}
805
806}
807