blob: 76717a71a0effe46bf3763974274307b892c0ba2 [file] [log] [blame]
/*
* Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include "net/http/structured_headers.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/mojom/fetch_api.mojom-blink.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_info.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_timing.h"
#include "third_party/blink/renderer/platform/network/http_names.h"
#include "third_party/blink/renderer/platform/network/http_parsers.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace {
template <typename Interface>
Vector<Interface> IsolatedCopy(const Vector<Interface>& src) {
Vector<Interface> result;
result.ReserveCapacity(src.size());
for (const auto& timestamp : src) {
result.push_back(timestamp.IsolatedCopy());
}
return result;
}
static const char kCacheControlHeader[] = "cache-control";
static const char kPragmaHeader[] = "pragma";
} // namespace
ResourceResponse::SignedCertificateTimestamp::SignedCertificateTimestamp(
const blink::WebURLResponse::SignedCertificateTimestamp& sct)
: status_(sct.status),
origin_(sct.origin),
log_description_(sct.log_description),
log_id_(sct.log_id),
timestamp_(sct.timestamp),
hash_algorithm_(sct.hash_algorithm),
signature_algorithm_(sct.signature_algorithm),
signature_data_(sct.signature_data) {}
ResourceResponse::SignedCertificateTimestamp
ResourceResponse::SignedCertificateTimestamp::IsolatedCopy() const {
return SignedCertificateTimestamp(
status_.IsolatedCopy(), origin_.IsolatedCopy(),
log_description_.IsolatedCopy(), log_id_.IsolatedCopy(), timestamp_,
hash_algorithm_.IsolatedCopy(), signature_algorithm_.IsolatedCopy(),
signature_data_.IsolatedCopy());
}
ResourceResponse::ResourceResponse() : is_null_(true) {}
ResourceResponse::ResourceResponse(const KURL& current_request_url)
: current_request_url_(current_request_url), is_null_(false) {}
ResourceResponse::ResourceResponse(const ResourceResponse&) = default;
ResourceResponse& ResourceResponse::operator=(const ResourceResponse&) =
default;
ResourceResponse::~ResourceResponse() = default;
bool ResourceResponse::IsHTTP() const {
return current_request_url_.ProtocolIsInHTTPFamily();
}
const KURL& ResourceResponse::CurrentRequestUrl() const {
return current_request_url_;
}
void ResourceResponse::SetCurrentRequestUrl(const KURL& url) {
is_null_ = false;
current_request_url_ = url;
}
KURL ResourceResponse::ResponseUrl() const {
// Ideally ResourceResponse would have a |url_list_| to match Fetch
// specification's URL list concept
// (https://fetch.spec.whatwg.org/#concept-response-url-list), and its
// last element would be returned here.
//
// Instead it has |url_list_via_service_worker_| which is only populated when
// the response came from a service worker, and that response was not created
// through `new Response()`. Use it when available.
if (!url_list_via_service_worker_.IsEmpty()) {
DCHECK(WasFetchedViaServiceWorker());
return url_list_via_service_worker_.back();
}
// Otherwise, use the current request URL. This is OK because the Fetch
// specification's "main fetch" algorithm[1] sets the response URL list to the
// request's URL list when the list isn't present. That step can't be
// implemented now because there is no |url_list_| memeber, but effectively
// the same thing happens by returning CurrentRequestUrl() here.
//
// [1] "If internalResponse’s URL list is empty, then set it to a clone of
// request’s URL list." at
// https://fetch.spec.whatwg.org/#ref-for-concept-response-url-list%E2%91%A4
return CurrentRequestUrl();
}
bool ResourceResponse::IsServiceWorkerPassThrough() const {
return cache_storage_cache_name_.IsEmpty() &&
!url_list_via_service_worker_.IsEmpty() &&
ResponseUrl() == CurrentRequestUrl();
}
const AtomicString& ResourceResponse::MimeType() const {
return mime_type_;
}
void ResourceResponse::SetMimeType(const AtomicString& mime_type) {
is_null_ = false;
// FIXME: MIME type is determined by HTTP Content-Type header. We should
// update the header, so that it doesn't disagree with m_mimeType.
mime_type_ = mime_type;
}
int64_t ResourceResponse::ExpectedContentLength() const {
return expected_content_length_;
}
void ResourceResponse::SetExpectedContentLength(
int64_t expected_content_length) {
is_null_ = false;
// FIXME: Content length is determined by HTTP Content-Length header. We
// should update the header, so that it doesn't disagree with
// m_expectedContentLength.
expected_content_length_ = expected_content_length;
}
const AtomicString& ResourceResponse::TextEncodingName() const {
return text_encoding_name_;
}
void ResourceResponse::SetTextEncodingName(const AtomicString& encoding_name) {
is_null_ = false;
// FIXME: Text encoding is determined by HTTP Content-Type header. We should
// update the header, so that it doesn't disagree with m_textEncodingName.
text_encoding_name_ = encoding_name;
}
int ResourceResponse::HttpStatusCode() const {
return http_status_code_;
}
void ResourceResponse::SetHttpStatusCode(int status_code) {
http_status_code_ = status_code;
}
const AtomicString& ResourceResponse::HttpStatusText() const {
return http_status_text_;
}
void ResourceResponse::SetHttpStatusText(const AtomicString& status_text) {
http_status_text_ = status_text;
}
const AtomicString& ResourceResponse::HttpHeaderField(
const AtomicString& name) const {
return http_header_fields_.Get(name);
}
void ResourceResponse::UpdateHeaderParsedState(const AtomicString& name) {
static const char kAgeHeader[] = "age";
static const char kDateHeader[] = "date";
static const char kExpiresHeader[] = "expires";
static const char kLastModifiedHeader[] = "last-modified";
if (EqualIgnoringASCIICase(name, kAgeHeader))
have_parsed_age_header_ = false;
else if (EqualIgnoringASCIICase(name, kCacheControlHeader) ||
EqualIgnoringASCIICase(name, kPragmaHeader))
cache_control_header_ = CacheControlHeader();
else if (EqualIgnoringASCIICase(name, kDateHeader))
have_parsed_date_header_ = false;
else if (EqualIgnoringASCIICase(name, kExpiresHeader))
have_parsed_expires_header_ = false;
else if (EqualIgnoringASCIICase(name, kLastModifiedHeader))
have_parsed_last_modified_header_ = false;
}
void ResourceResponse::SetSecurityDetails(
const String& protocol,
const String& key_exchange,
const String& key_exchange_group,
const String& cipher,
const String& mac,
const String& subject_name,
const Vector<String>& san_list,
const String& issuer,
time_t valid_from,
time_t valid_to,
const Vector<AtomicString>& certificate,
const SignedCertificateTimestampList& sct_list) {
DCHECK_NE(security_style_, SecurityStyle::kUnknown);
DCHECK_NE(security_style_, SecurityStyle::kNeutral);
security_details_ = SecurityDetails(
protocol, key_exchange, key_exchange_group, cipher, mac, subject_name,
san_list, issuer, valid_from, valid_to, certificate, sct_list);
}
bool ResourceResponse::IsCorsSameOrigin() const {
return network::cors::IsCorsSameOriginResponseType(response_type_);
}
bool ResourceResponse::IsCorsCrossOrigin() const {
return network::cors::IsCorsCrossOriginResponseType(response_type_);
}
void ResourceResponse::SetHttpHeaderField(const AtomicString& name,
const AtomicString& value) {
UpdateHeaderParsedState(name);
http_header_fields_.Set(name, value);
}
void ResourceResponse::AddHttpHeaderField(const AtomicString& name,
const AtomicString& value) {
UpdateHeaderParsedState(name);
HTTPHeaderMap::AddResult result = http_header_fields_.Add(name, value);
if (!result.is_new_entry)
result.stored_value->value = result.stored_value->value + ", " + value;
}
void ResourceResponse::AddHttpHeaderFieldWithMultipleValues(
const AtomicString& name,
const Vector<AtomicString>& values) {
if (values.IsEmpty())
return;
UpdateHeaderParsedState(name);
StringBuilder value_builder;
const auto it = http_header_fields_.Find(name);
if (it != http_header_fields_.end())
value_builder.Append(it->value);
for (const auto& value : values) {
if (!value_builder.IsEmpty())
value_builder.Append(", ");
value_builder.Append(value);
}
http_header_fields_.Set(name, value_builder.ToAtomicString());
}
void ResourceResponse::ClearHttpHeaderField(const AtomicString& name) {
http_header_fields_.Remove(name);
}
const HTTPHeaderMap& ResourceResponse::HttpHeaderFields() const {
return http_header_fields_;
}
bool ResourceResponse::CacheControlContainsNoCache() const {
if (!cache_control_header_.parsed) {
cache_control_header_ = ParseCacheControlDirectives(
http_header_fields_.Get(kCacheControlHeader),
http_header_fields_.Get(kPragmaHeader));
}
return cache_control_header_.contains_no_cache;
}
bool ResourceResponse::CacheControlContainsNoStore() const {
if (!cache_control_header_.parsed) {
cache_control_header_ = ParseCacheControlDirectives(
http_header_fields_.Get(kCacheControlHeader),
http_header_fields_.Get(kPragmaHeader));
}
return cache_control_header_.contains_no_store;
}
bool ResourceResponse::CacheControlContainsMustRevalidate() const {
if (!cache_control_header_.parsed) {
cache_control_header_ = ParseCacheControlDirectives(
http_header_fields_.Get(kCacheControlHeader),
http_header_fields_.Get(kPragmaHeader));
}
return cache_control_header_.contains_must_revalidate;
}
bool ResourceResponse::HasCacheValidatorFields() const {
static const char kLastModifiedHeader[] = "last-modified";
static const char kETagHeader[] = "etag";
return !http_header_fields_.Get(kLastModifiedHeader).IsEmpty() ||
!http_header_fields_.Get(kETagHeader).IsEmpty();
}
base::Optional<base::TimeDelta> ResourceResponse::CacheControlMaxAge() const {
if (!cache_control_header_.parsed) {
cache_control_header_ = ParseCacheControlDirectives(
http_header_fields_.Get(kCacheControlHeader),
http_header_fields_.Get(kPragmaHeader));
}
return cache_control_header_.max_age;
}
base::TimeDelta ResourceResponse::CacheControlStaleWhileRevalidate() const {
if (!cache_control_header_.parsed) {
cache_control_header_ = ParseCacheControlDirectives(
http_header_fields_.Get(kCacheControlHeader),
http_header_fields_.Get(kPragmaHeader));
}
if (!cache_control_header_.stale_while_revalidate ||
cache_control_header_.stale_while_revalidate.value() <
base::TimeDelta()) {
return base::TimeDelta();
}
return cache_control_header_.stale_while_revalidate.value();
}
static base::Optional<base::Time> ParseDateValueInHeader(
const HTTPHeaderMap& headers,
const AtomicString& header_name) {
const AtomicString& header_value = headers.Get(header_name);
if (header_value.IsEmpty())
return base::nullopt;
// This handles all date formats required by RFC2616:
// Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
// Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
base::Optional<base::Time> date = ParseDate(header_value);
if (date && date.value().is_max())
return base::nullopt;
return date;
}
base::Optional<base::Time> ResourceResponse::Date() const {
if (!have_parsed_date_header_) {
static const char kHeaderName[] = "date";
date_ = ParseDateValueInHeader(http_header_fields_, kHeaderName);
have_parsed_date_header_ = true;
}
return date_;
}
base::Optional<base::TimeDelta> ResourceResponse::Age() const {
if (!have_parsed_age_header_) {
static const char kHeaderName[] = "age";
const AtomicString& header_value = http_header_fields_.Get(kHeaderName);
bool ok;
double seconds = header_value.ToDouble(&ok);
if (!ok) {
age_ = base::nullopt;
} else {
age_ = base::TimeDelta::FromSecondsD(seconds);
}
have_parsed_age_header_ = true;
}
return age_;
}
base::Optional<base::Time> ResourceResponse::Expires() const {
if (!have_parsed_expires_header_) {
static const char kHeaderName[] = "expires";
expires_ = ParseDateValueInHeader(http_header_fields_, kHeaderName);
have_parsed_expires_header_ = true;
}
return expires_;
}
base::Optional<base::Time> ResourceResponse::LastModified() const {
if (!have_parsed_last_modified_header_) {
static const char kHeaderName[] = "last-modified";
last_modified_ = ParseDateValueInHeader(http_header_fields_, kHeaderName);
have_parsed_last_modified_header_ = true;
}
return last_modified_;
}
bool ResourceResponse::IsAttachment() const {
static const char kAttachmentString[] = "attachment";
String value = http_header_fields_.Get(http_names::kContentDisposition);
wtf_size_t loc = value.find(';');
if (loc != kNotFound)
value = value.Left(loc);
value = value.StripWhiteSpace();
return EqualIgnoringASCIICase(value, kAttachmentString);
}
AtomicString ResourceResponse::HttpContentType() const {
return ExtractMIMETypeFromMediaType(
HttpHeaderField(http_names::kContentType).LowerASCII());
}
bool ResourceResponse::WasCached() const {
return was_cached_;
}
void ResourceResponse::SetWasCached(bool value) {
was_cached_ = value;
}
bool ResourceResponse::ConnectionReused() const {
return connection_reused_;
}
void ResourceResponse::SetConnectionReused(bool connection_reused) {
connection_reused_ = connection_reused;
}
unsigned ResourceResponse::ConnectionID() const {
return connection_id_;
}
void ResourceResponse::SetConnectionID(unsigned connection_id) {
connection_id_ = connection_id;
}
ResourceLoadTiming* ResourceResponse::GetResourceLoadTiming() const {
return resource_load_timing_.get();
}
void ResourceResponse::SetResourceLoadTiming(
scoped_refptr<ResourceLoadTiming> resource_load_timing) {
resource_load_timing_ = std::move(resource_load_timing);
}
scoped_refptr<ResourceLoadInfo> ResourceResponse::GetResourceLoadInfo() const {
return resource_load_info_.get();
}
void ResourceResponse::SetResourceLoadInfo(
scoped_refptr<ResourceLoadInfo> load_info) {
resource_load_info_ = std::move(load_info);
}
void ResourceResponse::SetCTPolicyCompliance(CTPolicyCompliance compliance) {
ct_policy_compliance_ = compliance;
}
AtomicString ResourceResponse::ConnectionInfoString() const {
std::string connection_info_string =
net::HttpResponseInfo::ConnectionInfoToString(connection_info_);
return AtomicString(
reinterpret_cast<const LChar*>(connection_info_string.data()),
connection_info_string.length());
}
void ResourceResponse::SetEncodedDataLength(int64_t value) {
encoded_data_length_ = value;
}
void ResourceResponse::SetEncodedBodyLength(int64_t value) {
encoded_body_length_ = value;
}
void ResourceResponse::SetDecodedBodyLength(int64_t value) {
decoded_body_length_ = value;
}
network::mojom::CrossOriginEmbedderPolicyValue
ResourceResponse::GetCrossOriginEmbedderPolicy() const {
static constexpr char kHeaderName[] = "cross-origin-embedder-policy";
const std::string value = HttpHeaderField(kHeaderName).Utf8();
using Item = net::structured_headers::Item;
const auto item = net::structured_headers::ParseItem(value);
if (!item || item->item.Type() != Item::kTokenType ||
item->item.GetString() != "require-corp") {
return network::mojom::CrossOriginEmbedderPolicyValue::kNone;
}
return network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp;
}
STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersionUnknown,
ResourceResponse::kHTTPVersionUnknown);
STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_0_9,
ResourceResponse::kHTTPVersion_0_9);
STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_1_0,
ResourceResponse::kHTTPVersion_1_0);
STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_1_1,
ResourceResponse::kHTTPVersion_1_1);
STATIC_ASSERT_ENUM(WebURLResponse::kHTTPVersion_2_0,
ResourceResponse::kHTTPVersion_2_0);
} // namespace blink