blob: 309db261b460ae2a19b5195c2b7f050917b19d0c [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/fetch/fetch_response_data.h"
#include "storage/common/quota/padding_key.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom-blink.h"
#include "third_party/blink/renderer/core/fetch/fetch_header_list.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.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/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
using Type = network::mojom::FetchResponseType;
using ResponseSource = network::mojom::FetchResponseSource;
namespace blink {
namespace {
Vector<String> HeaderSetToVector(const HTTPHeaderSet& headers) {
Vector<String> result;
result.ReserveInitialCapacity(SafeCast<wtf_size_t>(headers.size()));
// HTTPHeaderSet stores headers using Latin1 encoding.
for (const auto& header : headers)
result.push_back(String(header.data(), header.size()));
return result;
}
} // namespace
FetchResponseData* FetchResponseData::Create() {
// "Unless stated otherwise, a response's url is null, status is 200, status
// message is the empty byte sequence, header list is an empty header list,
// and body is null."
return MakeGarbageCollected<FetchResponseData>(
Type::kDefault, ResponseSource::kUnspecified, 200, g_empty_atom);
}
FetchResponseData* FetchResponseData::CreateNetworkErrorResponse() {
// "A network error is a response whose status is always 0, status message
// is always the empty byte sequence, header list is aways an empty list,
// and body is always null."
return MakeGarbageCollected<FetchResponseData>(
Type::kError, ResponseSource::kUnspecified, 0, g_empty_atom);
}
FetchResponseData* FetchResponseData::CreateWithBuffer(
BodyStreamBuffer* buffer) {
FetchResponseData* response = FetchResponseData::Create();
response->buffer_ = buffer;
return response;
}
FetchResponseData* FetchResponseData::CreateBasicFilteredResponse() const {
DCHECK_EQ(type_, Type::kDefault);
// "A basic filtered response is a filtered response whose type is |basic|,
// header list excludes any headers in internal response's header list whose
// name is `Set-Cookie` or `Set-Cookie2`."
FetchResponseData* response = MakeGarbageCollected<FetchResponseData>(
Type::kBasic, response_source_, status_, status_message_);
response->SetURLList(url_list_);
for (const auto& header : header_list_->List()) {
if (FetchUtils::IsForbiddenResponseHeaderName(header.first))
continue;
response->header_list_->Append(header.first, header.second);
}
response->buffer_ = buffer_;
response->mime_type_ = mime_type_;
response->internal_response_ = const_cast<FetchResponseData*>(this);
return response;
}
FetchResponseData* FetchResponseData::CreateCorsFilteredResponse(
const HTTPHeaderSet& exposed_headers) const {
DCHECK_EQ(type_, Type::kDefault);
// "A CORS filtered response is a filtered response whose type is |CORS|,
// header list excludes all headers in internal response's header list,
// except those whose name is either one of `Cache-Control`,
// `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and
// `Pragma`, and except those whose name is one of the values resulting from
// parsing `Access-Control-Expose-Headers` in internal response's header
// list."
FetchResponseData* response = MakeGarbageCollected<FetchResponseData>(
Type::kCors, response_source_, status_, status_message_);
response->SetURLList(url_list_);
for (const auto& header : header_list_->List()) {
const String& name = header.first;
if (cors::IsCorsSafelistedResponseHeader(name) ||
(exposed_headers.find(name.Ascii()) != exposed_headers.end() &&
!FetchUtils::IsForbiddenResponseHeaderName(name))) {
response->header_list_->Append(name, header.second);
}
}
response->cors_exposed_header_names_ = exposed_headers;
response->buffer_ = buffer_;
response->mime_type_ = mime_type_;
response->internal_response_ = const_cast<FetchResponseData*>(this);
return response;
}
FetchResponseData* FetchResponseData::CreateOpaqueFilteredResponse() const {
DCHECK_EQ(type_, Type::kDefault);
// "An opaque filtered response is a filtered response whose type is
// 'opaque', url list is the empty list, status is 0, status message is the
// empty byte sequence, header list is the empty list, body is null, and
// cache state is 'none'."
//
// https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
FetchResponseData* response = MakeGarbageCollected<FetchResponseData>(
Type::kOpaque, response_source_, 0, g_empty_atom);
response->internal_response_ = const_cast<FetchResponseData*>(this);
return response;
}
FetchResponseData* FetchResponseData::CreateOpaqueRedirectFilteredResponse()
const {
DCHECK_EQ(type_, Type::kDefault);
// "An opaque filtered response is a filtered response whose type is
// 'opaqueredirect', status is 0, status message is the empty byte sequence,
// header list is the empty list, body is null, and cache state is 'none'."
//
// https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect
FetchResponseData* response = MakeGarbageCollected<FetchResponseData>(
Type::kOpaqueRedirect, response_source_, 0, g_empty_atom);
response->SetURLList(url_list_);
response->internal_response_ = const_cast<FetchResponseData*>(this);
return response;
}
const KURL* FetchResponseData::Url() const {
// "A response has an associated url. It is a pointer to the last response URL
// in response’s url list and null if response’s url list is the empty list."
if (url_list_.IsEmpty())
return nullptr;
return &url_list_.back();
}
uint16_t FetchResponseData::InternalStatus() const {
if (internal_response_) {
return internal_response_->Status();
}
return Status();
}
FetchHeaderList* FetchResponseData::InternalHeaderList() const {
if (internal_response_) {
return internal_response_->HeaderList();
}
return HeaderList();
}
String FetchResponseData::MimeType() const {
return mime_type_;
}
BodyStreamBuffer* FetchResponseData::InternalBuffer() const {
if (internal_response_) {
return internal_response_->buffer_;
}
return buffer_;
}
String FetchResponseData::InternalMIMEType() const {
if (internal_response_) {
return internal_response_->MimeType();
}
return mime_type_;
}
void FetchResponseData::SetURLList(const Vector<KURL>& url_list) {
url_list_ = url_list;
}
const Vector<KURL>& FetchResponseData::InternalURLList() const {
if (internal_response_) {
return internal_response_->url_list_;
}
return url_list_;
}
FetchResponseData* FetchResponseData::Clone(ScriptState* script_state,
ExceptionState& exception_state) {
FetchResponseData* new_response = Create();
new_response->type_ = type_;
new_response->padding_ = padding_;
new_response->response_source_ = response_source_;
if (termination_reason_) {
new_response->termination_reason_ = std::make_unique<TerminationReason>();
*new_response->termination_reason_ = *termination_reason_;
}
new_response->SetURLList(url_list_);
new_response->status_ = status_;
new_response->status_message_ = status_message_;
new_response->header_list_ = header_list_->Clone();
new_response->mime_type_ = mime_type_;
new_response->request_method_ = request_method_;
new_response->response_time_ = response_time_;
new_response->cache_storage_cache_name_ = cache_storage_cache_name_;
new_response->cors_exposed_header_names_ = cors_exposed_header_names_;
new_response->connection_info_ = connection_info_;
new_response->alpn_negotiated_protocol_ = alpn_negotiated_protocol_;
new_response->was_fetched_via_spdy_ = was_fetched_via_spdy_;
new_response->has_range_requested_ = has_range_requested_;
if (auth_challenge_info_) {
new_response->auth_challenge_info_ =
std::make_unique<net::AuthChallengeInfo>(*auth_challenge_info_);
}
switch (type_) {
case Type::kBasic:
case Type::kCors:
DCHECK(internal_response_);
DCHECK_EQ(buffer_, internal_response_->buffer_);
DCHECK_EQ(internal_response_->type_, Type::kDefault);
new_response->internal_response_ =
internal_response_->Clone(script_state, exception_state);
if (exception_state.HadException())
return nullptr;
buffer_ = internal_response_->buffer_;
new_response->buffer_ = new_response->internal_response_->buffer_;
break;
case Type::kDefault: {
DCHECK(!internal_response_);
if (buffer_) {
BodyStreamBuffer* new1 = nullptr;
BodyStreamBuffer* new2 = nullptr;
buffer_->Tee(&new1, &new2, exception_state);
if (exception_state.HadException())
return nullptr;
buffer_ = new1;
new_response->buffer_ = new2;
}
break;
}
case Type::kError:
DCHECK(!internal_response_);
DCHECK(!buffer_);
break;
case Type::kOpaque:
case Type::kOpaqueRedirect:
DCHECK(internal_response_);
DCHECK(!buffer_);
DCHECK_EQ(internal_response_->type_, Type::kDefault);
new_response->internal_response_ =
internal_response_->Clone(script_state, exception_state);
if (exception_state.HadException())
return nullptr;
break;
}
return new_response;
}
mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse(
const KURL& request_url) {
if (internal_response_) {
mojom::blink::FetchAPIResponsePtr response =
internal_response_->PopulateFetchAPIResponse(request_url);
response->response_type = type_;
response->response_source = response_source_;
response->cors_exposed_header_names =
HeaderSetToVector(cors_exposed_header_names_);
return response;
}
mojom::blink::FetchAPIResponsePtr response =
mojom::blink::FetchAPIResponse::New();
response->url_list = url_list_;
response->status_code = status_;
response->status_text = status_message_;
response->response_type = type_;
response->padding = padding_;
response->response_source = response_source_;
response->mime_type = mime_type_;
response->request_method = request_method_;
response->response_time = response_time_;
response->cache_storage_cache_name = cache_storage_cache_name_;
response->cors_exposed_header_names =
HeaderSetToVector(cors_exposed_header_names_);
response->connection_info = connection_info_;
response->alpn_negotiated_protocol = alpn_negotiated_protocol_;
response->was_fetched_via_spdy = was_fetched_via_spdy_;
response->has_range_requested = has_range_requested_;
for (const auto& header : HeaderList()->List())
response->headers.insert(header.first, header.second);
response->parsed_headers = ParseHeaders(
HeaderList()->GetAsRawString(status_, status_message_), request_url);
if (auth_challenge_info_) {
response->auth_challenge_info = *auth_challenge_info_;
}
return response;
}
void FetchResponseData::InitFromResourceResponse(
ExecutionContext* context,
network::mojom::FetchResponseType response_type,
const Vector<KURL>& request_url_list,
const AtomicString& request_method,
network::mojom::CredentialsMode request_credentials,
const ResourceResponse& response) {
SetStatus(response.HttpStatusCode());
if (response.CurrentRequestUrl().ProtocolIsAbout() ||
response.CurrentRequestUrl().ProtocolIsData() ||
response.CurrentRequestUrl().ProtocolIs("blob")) {
SetStatusMessage("OK");
} else {
SetStatusMessage(response.HttpStatusText());
}
for (auto& it : response.HttpHeaderFields())
HeaderList()->Append(it.key, it.value);
// Corresponds to https://fetch.spec.whatwg.org/#main-fetch step:
// "If |internalResponse|’s URL list is empty, then set it to a clone of
// |request|’s URL list."
if (response.UrlListViaServiceWorker().IsEmpty()) {
// Note: |UrlListViaServiceWorker()| is empty, unless the response came from
// a service worker, in which case it will only be empty if it was created
// through new Response().
SetURLList(request_url_list);
} else {
DCHECK(response.WasFetchedViaServiceWorker());
SetURLList(response.UrlListViaServiceWorker());
}
SetMimeType(response.MimeType());
SetRequestMethod(request_method);
SetResponseTime(response.ResponseTime());
if (response.WasCached()) {
SetResponseSource(network::mojom::FetchResponseSource::kHttpCache);
} else if (!response.WasFetchedViaServiceWorker()) {
SetResponseSource(network::mojom::FetchResponseSource::kNetwork);
}
SetConnectionInfo(response.ConnectionInfo());
// Some non-http responses, like data: url responses, will have a null
// |alpn_negotiated_protocol|. In these cases we leave the default
// value of "unknown".
if (!response.AlpnNegotiatedProtocol().IsNull())
SetAlpnNegotiatedProtocol(response.AlpnNegotiatedProtocol());
SetWasFetchedViaSpdy(response.WasFetchedViaSPDY());
SetHasRangeRequested(response.HasRangeRequested());
// Use the explicit padding in the response provided by a service worker
// or compute a new padding if necessary.
if (response.GetPadding()) {
SetPadding(response.GetPadding());
} else {
if (storage::ShouldPadResponseType(response_type)) {
int64_t padding = response.WasCached()
? storage::ComputeStableResponsePadding(
context->GetSecurityOrigin()->ToUrlOrigin(),
Url()->GetString().Utf8(), ResponseTime(),
request_method.Utf8())
: storage::ComputeRandomResponsePadding();
SetPadding(padding);
}
}
SetAuthChallengeInfo(response.AuthChallengeInfo());
}
FetchResponseData::FetchResponseData(Type type,
network::mojom::FetchResponseSource source,
uint16_t status,
AtomicString status_message)
: type_(type),
padding_(0),
response_source_(source),
status_(status),
status_message_(status_message),
header_list_(MakeGarbageCollected<FetchHeaderList>()),
response_time_(base::Time::Now()),
connection_info_(net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN),
alpn_negotiated_protocol_("unknown"),
was_fetched_via_spdy_(false),
has_range_requested_(false) {}
void FetchResponseData::SetAuthChallengeInfo(
const base::Optional<net::AuthChallengeInfo>& auth_challenge_info) {
if (auth_challenge_info) {
auth_challenge_info_ =
std::make_unique<net::AuthChallengeInfo>(*auth_challenge_info);
}
}
void FetchResponseData::ReplaceBodyStreamBuffer(BodyStreamBuffer* buffer) {
if (type_ == Type::kBasic || type_ == Type::kCors) {
DCHECK(internal_response_);
internal_response_->buffer_ = buffer;
buffer_ = buffer;
} else if (type_ == Type::kDefault) {
DCHECK(!internal_response_);
buffer_ = buffer;
}
}
void FetchResponseData::Trace(Visitor* visitor) const {
visitor->Trace(header_list_);
visitor->Trace(internal_response_);
visitor->Trace(buffer_);
}
} // namespace blink