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"
9 #include "mozilla/dom/FetchTypes.h"
10 #include "mozilla/ErrorResult.h"
12 #include "nsCharSeparatedTokenizer.h"
13 #include "nsContentUtils.h"
14 #include "nsIHttpChannel.h"
15 #include "nsIHttpHeaderVisitor.h"
16 #include "nsNetUtil.h"
17 #include "nsReadableUtils.h"
19 namespace mozilla::dom
{
21 InternalHeaders::InternalHeaders(nsTArray
<Entry
>&& aHeaders
,
22 HeadersGuardEnum aGuard
)
23 : mGuard(aGuard
), mList(std::move(aHeaders
)), mListDirty(true) {}
25 InternalHeaders::InternalHeaders(
26 const nsTArray
<HeadersEntry
>& aHeadersEntryList
, HeadersGuardEnum aGuard
)
27 : mGuard(aGuard
), mListDirty(true) {
28 for (const HeadersEntry
& headersEntry
: aHeadersEntryList
) {
29 mList
.AppendElement(Entry(headersEntry
.name(), headersEntry
.value()));
33 void InternalHeaders::ToIPC(nsTArray
<HeadersEntry
>& aIPCHeaders
,
34 HeadersGuardEnum
& aGuard
) {
38 for (Entry
& entry
: mList
) {
39 aIPCHeaders
.AppendElement(HeadersEntry(entry
.mName
, entry
.mValue
));
43 bool InternalHeaders::IsValidHeaderValue(const nsCString
& aLowerName
,
44 const nsCString
& aNormalizedValue
,
46 // Steps 2 to 6 for ::Set() and ::Append() in the spec.
49 if (IsInvalidName(aLowerName
, aRv
) || IsInvalidValue(aNormalizedValue
, aRv
)) {
54 if (IsImmutable(aRv
)) {
59 if (mGuard
== HeadersGuardEnum::Request
&&
60 IsForbiddenRequestHeader(aLowerName
)) {
65 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
66 nsAutoCString tempValue
;
67 Get(aLowerName
, tempValue
, aRv
);
69 if (tempValue
.IsVoid()) {
70 tempValue
= aNormalizedValue
;
72 tempValue
.Append(", ");
73 tempValue
.Append(aNormalizedValue
);
76 if (!nsContentUtils::IsCORSSafelistedRequestHeader(aLowerName
, tempValue
)) {
82 else if (IsForbiddenResponseHeader(aLowerName
)) {
89 void InternalHeaders::Append(const nsACString
& aName
, const nsACString
& aValue
,
92 nsAutoCString trimValue
;
93 NS_TrimHTTPWhitespace(aValue
, trimValue
);
96 nsAutoCString lowerName
;
97 ToLowerCase(aName
, lowerName
);
98 if (!IsValidHeaderValue(lowerName
, trimValue
, aRv
)) {
103 nsAutoCString
name(aName
);
104 ReuseExistingNameIfExists(name
);
106 mList
.AppendElement(Entry(name
, trimValue
));
109 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
110 RemovePrivilegedNoCorsRequestHeaders();
114 void InternalHeaders::RemovePrivilegedNoCorsRequestHeaders() {
117 // remove in reverse order to minimize copying
118 for (int32_t i
= mList
.Length() - 1; i
>= 0; --i
) {
119 if (IsPrivilegedNoCorsRequestHeaderName(mList
[i
].mName
)) {
120 mList
.RemoveElementAt(i
);
130 bool InternalHeaders::DeleteInternal(const nsCString
& aLowerName
,
134 // remove in reverse order to minimize copying
135 for (int32_t i
= mList
.Length() - 1; i
>= 0; --i
) {
136 if (mList
[i
].mName
.EqualsIgnoreCase(aLowerName
.get())) {
137 mList
.RemoveElementAt(i
);
149 void InternalHeaders::Delete(const nsACString
& aName
, ErrorResult
& aRv
) {
150 nsAutoCString lowerName
;
151 ToLowerCase(aName
, lowerName
);
154 if (IsInvalidName(lowerName
, aRv
)) {
159 if (IsImmutable(aRv
)) {
164 if (IsForbiddenRequestHeader(lowerName
)) {
169 if (mGuard
== HeadersGuardEnum::Request_no_cors
&&
170 !IsNoCorsSafelistedRequestHeaderName(lowerName
) &&
171 !IsPrivilegedNoCorsRequestHeaderName(lowerName
)) {
176 if (IsForbiddenResponseHeader(lowerName
)) {
181 if (!DeleteInternal(lowerName
, aRv
)) {
186 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
187 RemovePrivilegedNoCorsRequestHeaders();
191 void InternalHeaders::Get(const nsACString
& aName
, nsACString
& aValue
,
192 ErrorResult
& aRv
) const {
193 nsAutoCString lowerName
;
194 ToLowerCase(aName
, lowerName
);
196 if (IsInvalidName(lowerName
, aRv
)) {
200 GetInternal(lowerName
, aValue
, aRv
);
203 void InternalHeaders::GetInternal(const nsCString
& aLowerName
,
204 nsACString
& aValue
, ErrorResult
& aRv
) const {
205 const char* delimiter
= ", ";
206 bool firstValueFound
= false;
208 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
209 if (mList
[i
].mName
.EqualsIgnoreCase(aLowerName
.get())) {
210 if (firstValueFound
) {
213 aValue
+= mList
[i
].mValue
;
214 firstValueFound
= true;
218 // No value found, so return null to content
219 if (!firstValueFound
) {
220 aValue
.SetIsVoid(true);
224 void InternalHeaders::GetFirst(const nsACString
& aName
, nsACString
& aValue
,
225 ErrorResult
& aRv
) const {
226 nsAutoCString lowerName
;
227 ToLowerCase(aName
, lowerName
);
229 if (IsInvalidName(lowerName
, aRv
)) {
233 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
234 if (mList
[i
].mName
.EqualsIgnoreCase(lowerName
.get())) {
235 aValue
= mList
[i
].mValue
;
240 // No value found, so return null to content
241 aValue
.SetIsVoid(true);
244 bool InternalHeaders::Has(const nsACString
& aName
, ErrorResult
& aRv
) const {
245 nsAutoCString lowerName
;
246 ToLowerCase(aName
, lowerName
);
248 if (IsInvalidName(lowerName
, aRv
)) {
252 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
253 if (mList
[i
].mName
.EqualsIgnoreCase(lowerName
.get())) {
260 void InternalHeaders::Set(const nsACString
& aName
, const nsACString
& aValue
,
263 nsAutoCString trimValue
;
264 NS_TrimHTTPWhitespace(aValue
, trimValue
);
267 nsAutoCString lowerName
;
268 ToLowerCase(aName
, lowerName
);
269 if (!IsValidHeaderValue(lowerName
, trimValue
, aRv
)) {
276 int32_t firstIndex
= INT32_MAX
;
278 // remove in reverse order to minimize copying
279 for (int32_t i
= mList
.Length() - 1; i
>= 0; --i
) {
280 if (mList
[i
].mName
.EqualsIgnoreCase(lowerName
.get())) {
281 firstIndex
= std::min(firstIndex
, i
);
282 mList
.RemoveElementAt(i
);
286 if (firstIndex
< INT32_MAX
) {
287 Entry
* entry
= mList
.InsertElementAt(firstIndex
);
288 entry
->mName
= aName
;
289 entry
->mValue
= trimValue
;
291 mList
.AppendElement(Entry(aName
, trimValue
));
295 if (mGuard
== HeadersGuardEnum::Request_no_cors
) {
296 RemovePrivilegedNoCorsRequestHeaders();
300 void InternalHeaders::Clear() {
305 void InternalHeaders::SetGuard(HeadersGuardEnum aGuard
, ErrorResult
& aRv
) {
306 // The guard is only checked during ::Set() and ::Append() in the spec. It
307 // does not require revalidating headers already set.
311 InternalHeaders::~InternalHeaders() = default;
314 bool InternalHeaders::IsNoCorsSafelistedRequestHeaderName(
315 const nsCString
& aName
) {
316 return aName
.EqualsIgnoreCase("accept") ||
317 aName
.EqualsIgnoreCase("accept-language") ||
318 aName
.EqualsIgnoreCase("content-language") ||
319 aName
.EqualsIgnoreCase("content-type");
323 bool InternalHeaders::IsPrivilegedNoCorsRequestHeaderName(
324 const nsCString
& aName
) {
325 return aName
.EqualsIgnoreCase("range");
329 bool InternalHeaders::IsSimpleHeader(const nsCString
& aName
,
330 const nsACString
& aValue
) {
331 if (aValue
.Length() > 128) {
334 // Note, we must allow a null content-type value here to support
335 // get("content-type"), but the IsInvalidValue() check will prevent null
336 // from being set or appended.
337 return (aName
.EqualsIgnoreCase("accept") &&
338 nsContentUtils::IsAllowedNonCorsAccept(aValue
)) ||
339 (aName
.EqualsIgnoreCase("accept-language") &&
340 nsContentUtils::IsAllowedNonCorsLanguage(aValue
)) ||
341 (aName
.EqualsIgnoreCase("content-language") &&
342 nsContentUtils::IsAllowedNonCorsLanguage(aValue
)) ||
343 (aName
.EqualsIgnoreCase("content-type") &&
344 nsContentUtils::IsAllowedNonCorsContentType(aValue
));
348 bool InternalHeaders::IsRevalidationHeader(const nsCString
& aName
) {
349 return aName
.EqualsIgnoreCase("if-modified-since") ||
350 aName
.EqualsIgnoreCase("if-none-match") ||
351 aName
.EqualsIgnoreCase("if-unmodified-since") ||
352 aName
.EqualsIgnoreCase("if-match") ||
353 aName
.EqualsIgnoreCase("if-range");
357 bool InternalHeaders::IsInvalidName(const nsACString
& aName
, ErrorResult
& aRv
) {
358 if (!NS_IsValidHTTPToken(aName
)) {
359 aRv
.ThrowTypeError
<MSG_INVALID_HEADER_NAME
>(aName
);
367 bool InternalHeaders::IsInvalidValue(const nsACString
& aValue
,
369 if (!NS_IsReasonableHTTPHeaderValue(aValue
)) {
370 aRv
.ThrowTypeError
<MSG_INVALID_HEADER_VALUE
>(aValue
);
376 bool InternalHeaders::IsImmutable(ErrorResult
& aRv
) const {
377 if (mGuard
== HeadersGuardEnum::Immutable
) {
378 aRv
.ThrowTypeError("Headers are immutable and cannot be modified.");
384 bool InternalHeaders::IsForbiddenRequestHeader(const nsCString
& aName
) const {
385 return mGuard
== HeadersGuardEnum::Request
&&
386 nsContentUtils::IsForbiddenRequestHeader(aName
);
389 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
390 const nsCString
& aName
) const {
391 return mGuard
== HeadersGuardEnum::Request_no_cors
&&
392 !IsSimpleHeader(aName
, ""_ns
);
395 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
396 const nsCString
& aName
, const nsACString
& aValue
) const {
397 return mGuard
== HeadersGuardEnum::Request_no_cors
&&
398 !IsSimpleHeader(aName
, aValue
);
401 bool InternalHeaders::IsForbiddenResponseHeader(const nsCString
& aName
) const {
402 return mGuard
== HeadersGuardEnum::Response
&&
403 nsContentUtils::IsForbiddenResponseHeader(aName
);
406 void InternalHeaders::Fill(const InternalHeaders
& aInit
, ErrorResult
& aRv
) {
407 const nsTArray
<Entry
>& list
= aInit
.mList
;
408 for (uint32_t i
= 0; i
< list
.Length() && !aRv
.Failed(); ++i
) {
409 const Entry
& entry
= list
[i
];
410 Append(entry
.mName
, entry
.mValue
, aRv
);
414 void InternalHeaders::Fill(const Sequence
<Sequence
<nsCString
>>& aInit
,
416 for (uint32_t i
= 0; i
< aInit
.Length() && !aRv
.Failed(); ++i
) {
417 const Sequence
<nsCString
>& tuple
= aInit
[i
];
418 if (tuple
.Length() != 2) {
420 "Headers require name/value tuples when being initialized by a "
424 Append(tuple
[0], tuple
[1], aRv
);
428 void InternalHeaders::Fill(const Record
<nsCString
, nsCString
>& aInit
,
430 for (auto& entry
: aInit
.Entries()) {
431 Append(entry
.mKey
, entry
.mValue
, aRv
);
440 class FillHeaders final
: public nsIHttpHeaderVisitor
{
441 RefPtr
<InternalHeaders
> mInternalHeaders
;
443 ~FillHeaders() = default;
448 explicit FillHeaders(InternalHeaders
* aInternalHeaders
)
449 : mInternalHeaders(aInternalHeaders
) {
450 MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders
);
454 VisitHeader(const nsACString
& aHeader
, const nsACString
& aValue
) override
{
455 mInternalHeaders
->Append(aHeader
, aValue
, IgnoreErrors());
460 NS_IMPL_ISUPPORTS(FillHeaders
, nsIHttpHeaderVisitor
)
464 void InternalHeaders::FillResponseHeaders(nsIRequest
* aRequest
) {
465 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
);
470 RefPtr
<FillHeaders
> visitor
= new FillHeaders(this);
471 nsresult rv
= httpChannel
->VisitResponseHeaders(visitor
);
473 NS_WARNING("failed to fill headers");
477 bool InternalHeaders::HasOnlySimpleHeaders() const {
478 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
479 if (!IsSimpleHeader(mList
[i
].mName
, mList
[i
].mValue
)) {
487 bool InternalHeaders::HasRevalidationHeaders() const {
488 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
489 if (IsRevalidationHeader(mList
[i
].mName
)) {
498 already_AddRefed
<InternalHeaders
> InternalHeaders::BasicHeaders(
499 InternalHeaders
* aHeaders
) {
500 RefPtr
<InternalHeaders
> basic
= new InternalHeaders(*aHeaders
);
502 // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
504 basic
->Delete("Set-Cookie"_ns
, result
);
505 MOZ_ASSERT(!result
.Failed());
506 basic
->Delete("Set-Cookie2"_ns
, result
);
507 MOZ_ASSERT(!result
.Failed());
508 return basic
.forget();
512 already_AddRefed
<InternalHeaders
> InternalHeaders::CORSHeaders(
513 InternalHeaders
* aHeaders
, RequestCredentials aCredentialsMode
) {
514 RefPtr
<InternalHeaders
> cors
= new InternalHeaders(aHeaders
->mGuard
);
517 nsAutoCString acExposedNames
;
518 aHeaders
->Get("Access-Control-Expose-Headers"_ns
, acExposedNames
, result
);
519 MOZ_ASSERT(!result
.Failed());
521 bool allowAllHeaders
= false;
522 AutoTArray
<nsCString
, 5> exposeNamesArray
;
523 for (const nsACString
& token
:
524 nsCCharSeparatedTokenizer(acExposedNames
, ',').ToRange()) {
525 if (token
.IsEmpty()) {
529 if (!NS_IsValidHTTPToken(token
)) {
531 "Got invalid HTTP token in Access-Control-Expose-Headers. Header "
533 NS_WARNING(acExposedNames
.get());
534 exposeNamesArray
.Clear();
538 if (token
.EqualsLiteral("*") &&
539 aCredentialsMode
!= RequestCredentials::Include
) {
540 allowAllHeaders
= true;
543 exposeNamesArray
.AppendElement(token
);
546 nsCaseInsensitiveCStringArrayComparator comp
;
547 for (uint32_t i
= 0; i
< aHeaders
->mList
.Length(); ++i
) {
548 const Entry
& entry
= aHeaders
->mList
[i
];
549 if (allowAllHeaders
) {
550 cors
->Append(entry
.mName
, entry
.mValue
, result
);
551 MOZ_ASSERT(!result
.Failed());
552 } else if (entry
.mName
.EqualsIgnoreCase("cache-control") ||
553 entry
.mName
.EqualsIgnoreCase("content-language") ||
554 entry
.mName
.EqualsIgnoreCase("content-type") ||
555 entry
.mName
.EqualsIgnoreCase("content-length") ||
556 entry
.mName
.EqualsIgnoreCase("expires") ||
557 entry
.mName
.EqualsIgnoreCase("last-modified") ||
558 entry
.mName
.EqualsIgnoreCase("pragma") ||
559 exposeNamesArray
.Contains(entry
.mName
, comp
)) {
560 cors
->Append(entry
.mName
, entry
.mValue
, result
);
561 MOZ_ASSERT(!result
.Failed());
565 return cors
.forget();
568 void InternalHeaders::GetEntries(
569 nsTArray
<InternalHeaders::Entry
>& aEntries
) const {
570 MOZ_ASSERT(aEntries
.IsEmpty());
571 aEntries
.AppendElements(mList
);
574 void InternalHeaders::GetUnsafeHeaders(nsTArray
<nsCString
>& aNames
) const {
575 MOZ_ASSERT(aNames
.IsEmpty());
576 for (uint32_t i
= 0; i
< mList
.Length(); ++i
) {
577 const Entry
& header
= mList
[i
];
578 if (!InternalHeaders::IsSimpleHeader(header
.mName
, header
.mValue
)) {
579 aNames
.AppendElement(header
.mName
);
584 void InternalHeaders::MaybeSortList() {
587 bool Equals(const Entry
& aA
, const Entry
& aB
) const {
588 return aA
.mName
== aB
.mName
;
591 bool LessThan(const Entry
& aA
, const Entry
& aB
) const {
592 return aA
.mName
< aB
.mName
;
602 Comparator comparator
;
605 for (const Entry
& entry
: mList
) {
607 for (Entry
& sortedEntry
: mSortedList
) {
608 if (sortedEntry
.mName
.EqualsIgnoreCase(entry
.mName
.get())) {
609 sortedEntry
.mValue
+= ", ";
610 sortedEntry
.mValue
+= entry
.mValue
;
617 Entry newEntry
= entry
;
618 ToLowerCase(newEntry
.mName
);
619 mSortedList
.InsertElementSorted(newEntry
, comparator
);
624 void InternalHeaders::SetListDirty() {
629 void InternalHeaders::ReuseExistingNameIfExists(nsCString
& aName
) const {
630 for (const Entry
& entry
: mList
) {
631 if (entry
.mName
.EqualsIgnoreCase(aName
.get())) {
638 } // namespace mozilla::dom