1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/InternalHeaders.h"
10 #include "mozilla/dom/FetchTypes.h"
11 #include "mozilla/ErrorResult.h"
13 #include "nsCharSeparatedTokenizer.h"
14 #include "nsContentUtils.h"
15 #include "nsIHttpChannel.h"
16 #include "nsIHttpHeaderVisitor.h"
17 #include "nsNetUtil.h"
18 #include "nsReadableUtils.h"
20 namespace mozilla::dom
{
22 InternalHeaders::InternalHeaders(nsTArray
<Entry
>&& aHeaders
,
23 HeadersGuardEnum aGuard
)
24 : mGuard(aGuard
), mList(std::move(aHeaders
)), mListDirty(true) {}
26 InternalHeaders::InternalHeaders(
27 const nsTArray
<HeadersEntry
>& aHeadersEntryList
, HeadersGuardEnum aGuard
)
28 : mGuard(aGuard
), mListDirty(true) {
29 for (const HeadersEntry
& headersEntry
: aHeadersEntryList
) {
30 mList
.AppendElement(Entry(headersEntry
.name(), headersEntry
.value()));
34 void InternalHeaders::ToIPC(nsTArray
<HeadersEntry
>& aIPCHeaders
,
35 HeadersGuardEnum
& aGuard
) {
39 for (Entry
& entry
: mList
) {
40 aIPCHeaders
.AppendElement(HeadersEntry(entry
.mName
, entry
.mValue
));
44 bool InternalHeaders::IsValidHeaderValue(const nsCString
& aLowerName
,
45 const nsCString
& aNormalizedValue
,
47 // Steps 2 to 6 for ::Set() and ::Append() in the spec.
50 if (IsInvalidName(aLowerName
, aRv
) || IsInvalidValue(aNormalizedValue
, aRv
)) {
55 if (IsImmutable(aRv
)) {
60 if (mGuard
== HeadersGuardEnum::Request
) {
61 if (IsForbiddenRequestHeader(aLowerName
, aNormalizedValue
)) {
66 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
67 nsAutoCString tempValue
;
68 Get(aLowerName
, tempValue
, aRv
);
70 if (tempValue
.IsVoid()) {
71 tempValue
= aNormalizedValue
;
73 tempValue
.Append(", ");
74 tempValue
.Append(aNormalizedValue
);
77 if (!nsContentUtils::IsCORSSafelistedRequestHeader(aLowerName
, tempValue
)) {
83 else if (IsForbiddenResponseHeader(aLowerName
)) {
90 void InternalHeaders::Append(const nsACString
& aName
, const nsACString
& aValue
,
93 nsAutoCString trimValue
;
94 NS_TrimHTTPWhitespace(aValue
, trimValue
);
97 nsAutoCString lowerName
;
98 ToLowerCase(aName
, lowerName
);
99 if (!IsValidHeaderValue(lowerName
, trimValue
, aRv
)) {
104 nsAutoCString
name(aName
);
105 ReuseExistingNameIfExists(name
);
107 mList
.AppendElement(Entry(name
, trimValue
));
110 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
111 RemovePrivilegedNoCorsRequestHeaders();
115 void InternalHeaders::RemovePrivilegedNoCorsRequestHeaders() {
118 // remove in reverse order to minimize copying
119 for (int32_t i
= mList
.Length() - 1; i
>= 0; --i
) {
120 if (IsPrivilegedNoCorsRequestHeaderName(mList
[i
].mName
)) {
121 mList
.RemoveElementAt(i
);
131 bool InternalHeaders::DeleteInternal(const nsCString
& aLowerName
,
135 // remove in reverse order to minimize copying
136 for (int32_t i
= mList
.Length() - 1; i
>= 0; --i
) {
137 if (mList
[i
].mName
.EqualsIgnoreCase(aLowerName
.get())) {
138 mList
.RemoveElementAt(i
);
150 void InternalHeaders::Delete(const nsACString
& aName
, ErrorResult
& aRv
) {
151 // See https://fetch.spec.whatwg.org/#dom-headers-delete
152 nsAutoCString lowerName
;
153 ToLowerCase(aName
, lowerName
);
156 if (IsInvalidName(lowerName
, aRv
)) {
160 if (IsImmutable(aRv
)) {
165 GetInternal(lowerName
, value
, aRv
);
166 if (IsForbiddenRequestHeader(lowerName
, value
)) {
171 if (mGuard
== HeadersGuardEnum::Request_no_cors
&&
172 !IsNoCorsSafelistedRequestHeaderName(lowerName
) &&
173 !IsPrivilegedNoCorsRequestHeaderName(lowerName
)) {
177 if (IsForbiddenResponseHeader(lowerName
)) {
182 if (!DeleteInternal(lowerName
, aRv
)) {
187 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
188 RemovePrivilegedNoCorsRequestHeaders();
192 void InternalHeaders::Get(const nsACString
& aName
, nsACString
& aValue
,
193 ErrorResult
& aRv
) const {
194 nsAutoCString lowerName
;
195 ToLowerCase(aName
, lowerName
);
197 if (IsInvalidName(lowerName
, aRv
)) {
201 GetInternal(lowerName
, aValue
, aRv
);
204 void InternalHeaders::GetInternal(const nsCString
& aLowerName
,
205 nsACString
& aValue
, ErrorResult
& aRv
) const {
206 const char* delimiter
= ", ";
207 bool firstValueFound
= false;
209 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
210 if (mList
[i
].mName
.EqualsIgnoreCase(aLowerName
.get())) {
211 if (firstValueFound
) {
214 aValue
+= mList
[i
].mValue
;
215 firstValueFound
= true;
219 // No value found, so return null to content
220 if (!firstValueFound
) {
221 aValue
.SetIsVoid(true);
225 void InternalHeaders::GetSetCookie(nsTArray
<nsCString
>& aValues
) const {
226 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
227 if (mList
[i
].mName
.EqualsIgnoreCase("Set-Cookie")) {
228 aValues
.AppendElement(mList
[i
].mValue
);
233 void InternalHeaders::GetFirst(const nsACString
& aName
, nsACString
& aValue
,
234 ErrorResult
& aRv
) const {
235 nsAutoCString lowerName
;
236 ToLowerCase(aName
, lowerName
);
238 if (IsInvalidName(lowerName
, aRv
)) {
242 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
243 if (mList
[i
].mName
.EqualsIgnoreCase(lowerName
.get())) {
244 aValue
= mList
[i
].mValue
;
249 // No value found, so return null to content
250 aValue
.SetIsVoid(true);
253 bool InternalHeaders::Has(const nsACString
& aName
, ErrorResult
& aRv
) const {
254 nsAutoCString lowerName
;
255 ToLowerCase(aName
, lowerName
);
257 if (IsInvalidName(lowerName
, aRv
)) {
261 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
262 if (mList
[i
].mName
.EqualsIgnoreCase(lowerName
.get())) {
269 void InternalHeaders::Set(const nsACString
& aName
, const nsACString
& aValue
,
272 nsAutoCString trimValue
;
273 NS_TrimHTTPWhitespace(aValue
, trimValue
);
276 nsAutoCString lowerName
;
277 ToLowerCase(aName
, lowerName
);
278 if (!IsValidHeaderValue(lowerName
, trimValue
, aRv
)) {
285 int32_t firstIndex
= INT32_MAX
;
287 // remove in reverse order to minimize copying
288 for (int32_t i
= mList
.Length() - 1; i
>= 0; --i
) {
289 if (mList
[i
].mName
.EqualsIgnoreCase(lowerName
.get())) {
290 firstIndex
= std::min(firstIndex
, i
);
291 mList
.RemoveElementAt(i
);
295 if (firstIndex
< INT32_MAX
) {
296 Entry
* entry
= mList
.InsertElementAt(firstIndex
);
297 entry
->mName
= aName
;
298 entry
->mValue
= trimValue
;
300 mList
.AppendElement(Entry(aName
, trimValue
));
304 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
305 RemovePrivilegedNoCorsRequestHeaders();
309 void InternalHeaders::Clear() {
314 void InternalHeaders::SetGuard(HeadersGuardEnum aGuard
, ErrorResult
& aRv
) {
315 // The guard is only checked during ::Set() and ::Append() in the spec. It
316 // does not require revalidating headers already set.
320 InternalHeaders::~InternalHeaders() = default;
323 bool InternalHeaders::IsNoCorsSafelistedRequestHeaderName(
324 const nsCString
& aName
) {
325 return aName
.EqualsIgnoreCase("accept") ||
326 aName
.EqualsIgnoreCase("accept-language") ||
327 aName
.EqualsIgnoreCase("content-language") ||
328 aName
.EqualsIgnoreCase("content-type");
332 bool InternalHeaders::IsPrivilegedNoCorsRequestHeaderName(
333 const nsCString
& aName
) {
334 return aName
.EqualsIgnoreCase("range");
338 bool InternalHeaders::IsRevalidationHeader(const nsCString
& aName
) {
339 return aName
.EqualsIgnoreCase("if-modified-since") ||
340 aName
.EqualsIgnoreCase("if-none-match") ||
341 aName
.EqualsIgnoreCase("if-unmodified-since") ||
342 aName
.EqualsIgnoreCase("if-match") ||
343 aName
.EqualsIgnoreCase("if-range");
347 bool InternalHeaders::IsInvalidName(const nsACString
& aName
, ErrorResult
& aRv
) {
348 if (!NS_IsValidHTTPToken(aName
)) {
349 aRv
.ThrowTypeError
<MSG_INVALID_HEADER_NAME
>(aName
);
357 bool InternalHeaders::IsInvalidValue(const nsACString
& aValue
,
359 if (!NS_IsReasonableHTTPHeaderValue(aValue
)) {
360 aRv
.ThrowTypeError
<MSG_INVALID_HEADER_VALUE
>(aValue
);
366 bool InternalHeaders::IsImmutable(ErrorResult
& aRv
) const {
367 if (mGuard
== HeadersGuardEnum::Immutable
) {
368 aRv
.ThrowTypeError("Headers are immutable and cannot be modified.");
374 bool InternalHeaders::IsForbiddenRequestHeader(const nsCString
& aName
,
375 const nsACString
& aValue
) const {
376 return mGuard
== HeadersGuardEnum::Request
&&
377 nsContentUtils::IsForbiddenRequestHeader(aName
, aValue
);
380 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
381 const nsCString
& aName
) const {
382 return mGuard
== HeadersGuardEnum::Request_no_cors
&&
383 !nsContentUtils::IsCORSSafelistedRequestHeader(aName
, ""_ns
);
386 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
387 const nsCString
& aName
, const nsACString
& aValue
) const {
388 return mGuard
== HeadersGuardEnum::Request_no_cors
&&
389 !nsContentUtils::IsCORSSafelistedRequestHeader(aName
, aValue
);
392 bool InternalHeaders::IsForbiddenResponseHeader(const nsCString
& aName
) const {
393 return mGuard
== HeadersGuardEnum::Response
&&
394 nsContentUtils::IsForbiddenResponseHeader(aName
);
397 void InternalHeaders::Fill(const InternalHeaders
& aInit
, ErrorResult
& aRv
) {
398 const nsTArray
<Entry
>& list
= aInit
.mList
;
399 for (uint32_t i
= 0; i
< list
.Length() && !aRv
.Failed(); ++i
) {
400 const Entry
& entry
= list
[i
];
401 Append(entry
.mName
, entry
.mValue
, aRv
);
405 void InternalHeaders::Fill(const Sequence
<Sequence
<nsCString
>>& aInit
,
407 for (uint32_t i
= 0; i
< aInit
.Length() && !aRv
.Failed(); ++i
) {
408 const Sequence
<nsCString
>& tuple
= aInit
[i
];
409 if (tuple
.Length() != 2) {
411 "Headers require name/value tuples when being initialized by a "
415 Append(tuple
[0], tuple
[1], aRv
);
419 void InternalHeaders::Fill(const Record
<nsCString
, nsCString
>& aInit
,
421 for (auto& entry
: aInit
.Entries()) {
422 Append(entry
.mKey
, entry
.mValue
, aRv
);
431 class FillHeaders final
: public nsIHttpHeaderVisitor
{
432 RefPtr
<InternalHeaders
> mInternalHeaders
;
434 ~FillHeaders() = default;
439 explicit FillHeaders(InternalHeaders
* aInternalHeaders
)
440 : mInternalHeaders(aInternalHeaders
) {
441 MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders
);
445 VisitHeader(const nsACString
& aHeader
, const nsACString
& aValue
) override
{
446 mInternalHeaders
->Append(aHeader
, aValue
, IgnoreErrors());
451 NS_IMPL_ISUPPORTS(FillHeaders
, nsIHttpHeaderVisitor
)
455 void InternalHeaders::FillResponseHeaders(nsIRequest
* aRequest
) {
456 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
);
461 RefPtr
<FillHeaders
> visitor
= new FillHeaders(this);
462 nsresult rv
= httpChannel
->VisitResponseHeaders(visitor
);
464 NS_WARNING("failed to fill headers");
468 bool InternalHeaders::HasOnlySimpleHeaders() const {
469 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
470 if (!nsContentUtils::IsCORSSafelistedRequestHeader(mList
[i
].mName
,
479 bool InternalHeaders::HasRevalidationHeaders() const {
480 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
481 if (IsRevalidationHeader(mList
[i
].mName
)) {
490 already_AddRefed
<InternalHeaders
> InternalHeaders::BasicHeaders(
491 InternalHeaders
* aHeaders
) {
492 RefPtr
<InternalHeaders
> basic
= new InternalHeaders(*aHeaders
);
494 // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
496 basic
->Delete("Set-Cookie"_ns
, result
);
497 MOZ_ASSERT(!result
.Failed());
498 basic
->Delete("Set-Cookie2"_ns
, result
);
499 MOZ_ASSERT(!result
.Failed());
500 return basic
.forget();
504 already_AddRefed
<InternalHeaders
> InternalHeaders::CORSHeaders(
505 InternalHeaders
* aHeaders
, RequestCredentials aCredentialsMode
) {
506 RefPtr
<InternalHeaders
> cors
= new InternalHeaders(aHeaders
->mGuard
);
509 nsAutoCString acExposedNames
;
510 aHeaders
->Get("Access-Control-Expose-Headers"_ns
, acExposedNames
, result
);
511 MOZ_ASSERT(!result
.Failed());
513 bool allowAllHeaders
= false;
514 AutoTArray
<nsCString
, 5> exposeNamesArray
;
515 for (const nsACString
& token
:
516 nsCCharSeparatedTokenizer(acExposedNames
, ',').ToRange()) {
517 if (token
.IsEmpty()) {
521 if (!NS_IsValidHTTPToken(token
)) {
523 "Got invalid HTTP token in Access-Control-Expose-Headers. Header "
525 NS_WARNING(acExposedNames
.get());
526 exposeNamesArray
.Clear();
530 if (token
.EqualsLiteral("*") &&
531 aCredentialsMode
!= RequestCredentials::Include
) {
532 allowAllHeaders
= true;
535 exposeNamesArray
.AppendElement(token
);
538 nsCaseInsensitiveCStringArrayComparator comp
;
539 for (uint32_t i
= 0; i
< aHeaders
->mList
.Length(); ++i
) {
540 const Entry
& entry
= aHeaders
->mList
[i
];
541 if (allowAllHeaders
) {
542 cors
->Append(entry
.mName
, entry
.mValue
, result
);
543 MOZ_ASSERT(!result
.Failed());
544 } else if (entry
.mName
.EqualsIgnoreCase("cache-control") ||
545 entry
.mName
.EqualsIgnoreCase("content-language") ||
546 entry
.mName
.EqualsIgnoreCase("content-type") ||
547 entry
.mName
.EqualsIgnoreCase("content-length") ||
548 entry
.mName
.EqualsIgnoreCase("expires") ||
549 entry
.mName
.EqualsIgnoreCase("last-modified") ||
550 entry
.mName
.EqualsIgnoreCase("pragma") ||
551 exposeNamesArray
.Contains(entry
.mName
, comp
)) {
552 cors
->Append(entry
.mName
, entry
.mValue
, result
);
553 MOZ_ASSERT(!result
.Failed());
557 return cors
.forget();
560 void InternalHeaders::GetEntries(
561 nsTArray
<InternalHeaders::Entry
>& aEntries
) const {
562 MOZ_ASSERT(aEntries
.IsEmpty());
563 aEntries
.AppendElements(mList
);
566 void InternalHeaders::GetUnsafeHeaders(nsTArray
<nsCString
>& aNames
) const {
567 MOZ_ASSERT(aNames
.IsEmpty());
568 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
569 const Entry
& header
= mList
[i
];
570 if (!nsContentUtils::IsCORSSafelistedRequestHeader(header
.mName
,
572 aNames
.AppendElement(header
.mName
);
577 void InternalHeaders::MaybeSortList() {
580 bool Equals(const Entry
& aA
, const Entry
& aB
) const {
581 return aA
.mName
== aB
.mName
;
584 bool LessThan(const Entry
& aA
, const Entry
& aB
) const {
585 return aA
.mName
< aB
.mName
;
595 Comparator comparator
;
598 for (const Entry
& entry
: mList
) {
601 // We combine every header but Set-Cookie.
602 if (!entry
.mName
.EqualsIgnoreCase("Set-Cookie")) {
603 for (Entry
& sortedEntry
: mSortedList
) {
604 if (sortedEntry
.mName
.EqualsIgnoreCase(entry
.mName
.get())) {
605 sortedEntry
.mValue
+= ", ";
606 sortedEntry
.mValue
+= entry
.mValue
;
614 Entry newEntry
= entry
;
615 ToLowerCase(newEntry
.mName
);
616 mSortedList
.InsertElementSorted(newEntry
, comparator
);
621 void InternalHeaders::SetListDirty() {
626 void InternalHeaders::ReuseExistingNameIfExists(nsCString
& aName
) const {
627 for (const Entry
& entry
: mList
) {
628 if (entry
.mName
.EqualsIgnoreCase(aName
.get())) {
635 } // namespace mozilla::dom