blob: 95da6ece978c882086a9dd46e14b259204ecb1bc [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/headers.h"
#include "third_party/blink/renderer/bindings/core/v8/byte_string_sequence_sequence_or_byte_string_byte_string_record.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
#include "third_party/blink/renderer/core/dom/iterator.h"
#include "third_party/blink/renderer/platform/bindings/exception_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/wtf/text/wtf_string.h"
namespace blink {
namespace {
class HeadersIterationSource final
: public PairIterable<String, String>::IterationSource {
public:
explicit HeadersIterationSource(const FetchHeaderList* headers)
: headers_(headers->SortAndCombine()), current_(0) {}
bool Next(ScriptState* script_state,
String& key,
String& value,
ExceptionState& exception) override {
// This simply advances an index and returns the next value if any; the
// iterated list is not exposed to script so it will never be mutated
// during iteration.
if (current_ >= headers_.size())
return false;
const FetchHeaderList::Header& header = headers_.at(current_++);
key = header.first;
value = header.second;
return true;
}
private:
Vector<std::pair<String, String>> headers_;
wtf_size_t current_;
};
} // namespace
Headers* Headers::Create(ExceptionState&) {
return new Headers;
}
Headers* Headers::Create(const HeadersInit& init,
ExceptionState& exception_state) {
// "The Headers(|init|) constructor, when invoked, must run these steps:"
// "1. Let |headers| be a new Headers object whose guard is "none".
Headers* headers = Create(exception_state);
// "2. If |init| is given, fill headers with |init|. Rethrow any exception."
headers->FillWith(init, exception_state);
// "3. Return |headers|."
return headers;
}
Headers* Headers::Create(FetchHeaderList* header_list) {
return new Headers(header_list);
}
Headers* Headers::Clone() const {
FetchHeaderList* header_list = header_list_->Clone();
Headers* headers = Create(header_list);
headers->guard_ = guard_;
return headers;
}
void Headers::append(const String& name,
const String& value,
ExceptionState& exception_state) {
// "To append a name/value (|name|/|value|) pair to a Headers object
// (|headers|), run these steps:"
// "1. Normalize |value|."
const String normalized_value = FetchUtils::NormalizeHeaderValue(value);
// "2. If |name| is not a name or |value| is not a value, throw a
// TypeError."
if (!FetchHeaderList::IsValidHeaderName(name)) {
exception_state.ThrowTypeError("Invalid name");
return;
}
if (!FetchHeaderList::IsValidHeaderValue(normalized_value)) {
exception_state.ThrowTypeError("Invalid value");
return;
}
// "3. If guard is |immutable|, throw a TypeError."
if (guard_ == kImmutableGuard) {
exception_state.ThrowTypeError("Headers are immutable");
return;
}
// "4. Otherwise, if guard is |request| and |name| is a forbidden header
// name, return."
if (guard_ == kRequestGuard && CORS::IsForbiddenHeaderName(name))
return;
// "5. Otherwise, if guard is |request-no-CORS| and |name|/|value| is not a
// no-CORS-safelisted header, return."
if (guard_ == kRequestNoCORSGuard &&
!CORS::IsNoCORSSafelistedHeader(name, normalized_value)) {
return;
}
// "6. Otherwise, if guard is |response| and |name| is a forbidden response
// header name, return."
if (guard_ == kResponseGuard &&
FetchUtils::IsForbiddenResponseHeaderName(name)) {
return;
}
// "7. Append |name|/|value| to header list."
header_list_->Append(name, normalized_value);
}
void Headers::remove(const String& name, ExceptionState& exception_state) {
// "The delete(|name|) method, when invoked, must run these steps:"
// "1. If name is not a name, throw a TypeError."
if (!FetchHeaderList::IsValidHeaderName(name)) {
exception_state.ThrowTypeError("Invalid name");
return;
}
// "2. If guard is |immutable|, throw a TypeError."
if (guard_ == kImmutableGuard) {
exception_state.ThrowTypeError("Headers are immutable");
return;
}
// "3. Otherwise, if guard is |request| and |name| is a forbidden header
// name, return."
if (guard_ == kRequestGuard && CORS::IsForbiddenHeaderName(name))
return;
// "4. Otherwise, if guard is |request-no-CORS| and |name|/`invalid` is not
// a no-CORS-safelisted header, return."
if (guard_ == kRequestNoCORSGuard &&
!CORS::IsNoCORSSafelistedHeader(name, "invalid")) {
return;
}
// "5. Otherwise, if guard is |response| and |name| is a forbidden response
// header name, return."
if (guard_ == kResponseGuard &&
FetchUtils::IsForbiddenResponseHeaderName(name)) {
return;
}
// "6. Delete |name| from header list."
header_list_->Remove(name);
}
String Headers::get(const String& name, ExceptionState& exception_state) {
// "The get(|name|) method, when invoked, must run these steps:"
// "1. If |name| is not a name, throw a TypeError."
if (!FetchHeaderList::IsValidHeaderName(name)) {
exception_state.ThrowTypeError("Invalid name");
return String();
}
// "2. If there is no header in header list whose name is |name|,
// return null."
// "3. Return the combined value given |name| and header list."
String result;
header_list_->Get(name, result);
return result;
}
bool Headers::has(const String& name, ExceptionState& exception_state) {
// "The has(|name|) method, when invoked, must run these steps:"
// "1. If |name| is not a name, throw a TypeError."
if (!FetchHeaderList::IsValidHeaderName(name)) {
exception_state.ThrowTypeError("Invalid name");
return false;
}
// "2. Return true if there is a header in header list whose name is |name|,
// and false otherwise."
return header_list_->Has(name);
}
void Headers::set(const String& name,
const String& value,
ExceptionState& exception_state) {
// "The set(|name|, |value|) method, when invoked, must run these steps:"
// "1. Normalize |value|."
const String normalized_value = FetchUtils::NormalizeHeaderValue(value);
// "2. If |name| is not a name or |value| is not a value, throw a
// TypeError."
if (!FetchHeaderList::IsValidHeaderName(name)) {
exception_state.ThrowTypeError("Invalid name");
return;
}
if (!FetchHeaderList::IsValidHeaderValue(normalized_value)) {
exception_state.ThrowTypeError("Invalid value");
return;
}
// "3. If guard is |immutable|, throw a TypeError."
if (guard_ == kImmutableGuard) {
exception_state.ThrowTypeError("Headers are immutable");
return;
}
// "4. Otherwise, if guard is |request| and |name| is a forbidden header
// name, return."
if (guard_ == kRequestGuard && CORS::IsForbiddenHeaderName(name))
return;
// "5. Otherwise, if guard is |request-no-CORS| and |name|/|value| is not a
// no-CORS-safelisted header, return."
if (guard_ == kRequestNoCORSGuard &&
!CORS::IsNoCORSSafelistedHeader(name, normalized_value)) {
return;
}
// "6. Otherwise, if guard is |response| and |name| is a forbidden response
// header name, return."
if (guard_ == kResponseGuard &&
FetchUtils::IsForbiddenResponseHeaderName(name)) {
return;
}
// "7. Set |name|/|value| in header list."
header_list_->Set(name, normalized_value);
}
// This overload is not called directly by Web APIs, but rather by other C++
// classes. For example, when initializing a Request object it is possible that
// a Request's Headers must be filled with an existing Headers object.
void Headers::FillWith(const Headers* object, ExceptionState& exception_state) {
DCHECK_EQ(header_list_->size(), 0U);
for (const auto& header : object->header_list_->List()) {
append(header.first, header.second, exception_state);
if (exception_state.HadException())
return;
}
}
void Headers::FillWith(const HeadersInit& init,
ExceptionState& exception_state) {
DCHECK_EQ(header_list_->size(), 0U);
if (init.IsByteStringSequenceSequence()) {
FillWith(init.GetAsByteStringSequenceSequence(), exception_state);
} else if (init.IsByteStringByteStringRecord()) {
FillWith(init.GetAsByteStringByteStringRecord(), exception_state);
} else {
NOTREACHED();
}
}
void Headers::FillWith(const Vector<Vector<String>>& object,
ExceptionState& exception_state) {
DCHECK(!header_list_->size());
// "1. If |object| is a sequence, then for each |header| in |object|, run
// these substeps:
// 1. If |header| does not contain exactly two items, then throw a
// TypeError.
// 2. Append |header|’s first item/|header|’s second item to |headers|.
// Rethrow any exception."
for (wtf_size_t i = 0; i < object.size(); ++i) {
if (object[i].size() != 2) {
exception_state.ThrowTypeError("Invalid value");
return;
}
append(object[i][0], object[i][1], exception_state);
if (exception_state.HadException())
return;
}
}
void Headers::FillWith(const Vector<std::pair<String, String>>& object,
ExceptionState& exception_state) {
DCHECK(!header_list_->size());
for (const auto& item : object) {
append(item.first, item.second, exception_state);
if (exception_state.HadException())
return;
}
}
Headers::Headers()
: header_list_(FetchHeaderList::Create()), guard_(kNoneGuard) {}
Headers::Headers(FetchHeaderList* header_list)
: header_list_(header_list), guard_(kNoneGuard) {}
void Headers::Trace(blink::Visitor* visitor) {
visitor->Trace(header_list_);
ScriptWrappable::Trace(visitor);
}
PairIterable<String, String>::IterationSource* Headers::StartIteration(
ScriptState*,
ExceptionState&) {
return new HeadersIterationSource(header_list_);
}
} // namespace blink