| // 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 "third_party/blink/public/platform/modules/service_worker/web_service_worker_response.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" |
| |
| using Type = network::mojom::FetchResponseType; |
| using ResponseSource = network::mojom::FetchResponseSource; |
| |
| namespace blink { |
| |
| namespace { |
| |
| WebVector<WebString> HeaderSetToWebVector(const WebHTTPHeaderSet& headers) { |
| // Can't just pass *headers to the WebVector constructor because HashSet |
| // iterators are not stl iterator compatible. |
| WebVector<WebString> result(static_cast<size_t>(headers.size())); |
| int idx = 0; |
| for (const auto& header : headers) |
| result[idx++] = WebString::FromASCII(header); |
| return result; |
| } |
| |
| 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::IsOnAccessControlResponseHeaderWhitelist(name) || |
| (exposed_headers.find(name.Ascii().data()) != 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; |
| } |
| |
| void FetchResponseData::PopulateWebServiceWorkerResponse( |
| WebServiceWorkerResponse& response) { |
| if (internal_response_) { |
| internal_response_->PopulateWebServiceWorkerResponse(response); |
| response.SetResponseType(type_); |
| response.SetResponseSource(response_source_); |
| response.SetCorsExposedHeaderNames( |
| HeaderSetToWebVector(cors_exposed_header_names_)); |
| return; |
| } |
| response.SetURLList(url_list_); |
| response.SetStatus(Status()); |
| response.SetStatusText(StatusMessage()); |
| response.SetResponseType(type_); |
| response.SetResponseSource(response_source_); |
| response.SetResponseTime(ResponseTime()); |
| response.SetCacheStorageCacheName(CacheStorageCacheName()); |
| response.SetCorsExposedHeaderNames( |
| HeaderSetToWebVector(cors_exposed_header_names_)); |
| for (const auto& header : HeaderList()->List()) { |
| response.AppendHeader(header.first, header.second); |
| } |
| } |
| |
| 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); |
| return response; |
| } |
| |
| FetchResponseData::FetchResponseData(Type type, |
| network::mojom::FetchResponseSource source, |
| unsigned short status, |
| AtomicString status_message) |
| : type_(type), |
| response_source_(source), |
| status_(status), |
| status_message_(status_message), |
| header_list_(FetchHeaderList::Create()), |
| 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 |