blob: e8dfb169fb64d8cc11741a73677cfe3b0273ed5d [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 "services/network/public/cpp/content_security_policy.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/content_security_policy.mojom-blink.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/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
using Type = network::mojom::FetchResponseType;
using ResponseSource = network::mojom::FetchResponseSource;
namespace blink {
namespace {
Vector<String> HeaderSetToVector(const WebHTTPHeaderSet& headers) {
Vector<String> result;
result.ReserveInitialCapacity(SafeCast<wtf_size_t>(headers.size()));
// WebHTTPHeaderSet 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 WebHTTPHeaderSet& 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();
}
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->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->response_time_ = response_time_;
new_response->cache_storage_cache_name_ = cache_storage_cache_name_;
new_response->cors_exposed_header_names_ = cors_exposed_header_names_;
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() {
if (internal_response_) {
mojom::blink::FetchAPIResponsePtr response =
internal_response_->PopulateFetchAPIResponse();
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->response_source = response_source_;
response->response_time = response_time_;
response->cache_storage_cache_name = cache_storage_cache_name_;
response->cors_exposed_header_names =
HeaderSetToVector(cors_exposed_header_names_);
for (const auto& header : HeaderList()->List())
response->headers.insert(header.first, header.second);
// Check if there's a Content-Security-Policy header and parse it if
// necessary.
if (base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors)) {
String content_security_policy_header;
if (HeaderList()->Get("content-security-policy",
content_security_policy_header)) {
network::ContentSecurityPolicy policy;
if (policy.Parse(StringUTF8Adaptor(content_security_policy_header)
.AsStringPiece())) {
const network::mojom::CSPSourceListPtr& frame_ancestors_directive =
policy.content_security_policy_ptr()->frame_ancestors;
if (frame_ancestors_directive) {
// Convert network::mojom::ContentSecurityPolicy to
// network::mojom::blink::ContentSecurityPolicy.
auto blink_frame_ancestors =
network::mojom::blink::CSPSourceList::New();
for (auto& csp_source : frame_ancestors_directive->sources) {
blink_frame_ancestors->sources.push_back(
network::mojom::blink::CSPSource::New(
String::FromUTF8(csp_source->scheme),
String::FromUTF8(csp_source->host), csp_source->port,
String::FromUTF8(csp_source->path),
csp_source->is_host_wildcard, csp_source->is_port_wildcard,
csp_source->allow_self));
}
response->content_security_policy =
network::mojom::blink::ContentSecurityPolicy::New(
std::move(blink_frame_ancestors));
}
}
}
}
return response;
}
FetchResponseData::FetchResponseData(Type type,
network::mojom::FetchResponseSource source,
uint16_t status,
AtomicString status_message)
: type_(type),
response_source_(source),
status_(status),
status_message_(status_message),
header_list_(MakeGarbageCollected<FetchHeaderList>()),
response_time_(base::Time::Now()) {}
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(blink::Visitor* visitor) {
visitor->Trace(header_list_);
visitor->Trace(internal_response_);
visitor->Trace(buffer_);
}
} // namespace blink