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/. */
6 
7 #include "mozilla/dom/InternalHeaders.h"
8 
9 #include "mozilla/dom/FetchTypes.h"
10 #include "mozilla/ErrorResult.h"
11 
12 #include "nsCharSeparatedTokenizer.h"
13 #include "nsContentUtils.h"
14 #include "nsIHttpHeaderVisitor.h"
15 #include "nsNetUtil.h"
16 #include "nsReadableUtils.h"
17 
18 namespace mozilla {
19 namespace dom {
20 
InternalHeaders(const nsTArray<Entry> && aHeaders,HeadersGuardEnum aGuard)21 InternalHeaders::InternalHeaders(const nsTArray<Entry>&& aHeaders,
22                                  HeadersGuardEnum aGuard)
23     : mGuard(aGuard), mList(aHeaders), mListDirty(true) {}
24 
InternalHeaders(const nsTArray<HeadersEntry> & aHeadersEntryList,HeadersGuardEnum aGuard)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()));
30   }
31 }
32 
ToIPC(nsTArray<HeadersEntry> & aIPCHeaders,HeadersGuardEnum & aGuard)33 void InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders,
34                             HeadersGuardEnum& aGuard) {
35   aGuard = mGuard;
36 
37   aIPCHeaders.Clear();
38   for (Entry& entry : mList) {
39     aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue));
40   }
41 }
42 
Append(const nsACString & aName,const nsACString & aValue,ErrorResult & aRv)43 void InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
44                              ErrorResult& aRv) {
45   nsAutoCString lowerName;
46   ToLowerCase(aName, lowerName);
47   nsAutoCString trimValue;
48   NS_TrimHTTPWhitespace(aValue, trimValue);
49 
50   if (IsInvalidMutableHeader(lowerName, trimValue, aRv)) {
51     return;
52   }
53 
54   SetListDirty();
55 
56   mList.AppendElement(Entry(lowerName, trimValue));
57 }
58 
Delete(const nsACString & aName,ErrorResult & aRv)59 void InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv) {
60   nsAutoCString lowerName;
61   ToLowerCase(aName, lowerName);
62 
63   if (IsInvalidMutableHeader(lowerName, aRv)) {
64     return;
65   }
66 
67   SetListDirty();
68 
69   // remove in reverse order to minimize copying
70   for (int32_t i = mList.Length() - 1; i >= 0; --i) {
71     if (lowerName == mList[i].mName) {
72       mList.RemoveElementAt(i);
73     }
74   }
75 }
76 
Get(const nsACString & aName,nsACString & aValue,ErrorResult & aRv) const77 void InternalHeaders::Get(const nsACString& aName, nsACString& aValue,
78                           ErrorResult& aRv) const {
79   nsAutoCString lowerName;
80   ToLowerCase(aName, lowerName);
81 
82   if (IsInvalidName(lowerName, aRv)) {
83     return;
84   }
85 
86   const char* delimiter = ", ";
87   bool firstValueFound = false;
88 
89   for (uint32_t i = 0; i < mList.Length(); ++i) {
90     if (lowerName == mList[i].mName) {
91       if (firstValueFound) {
92         aValue += delimiter;
93       }
94       aValue += mList[i].mValue;
95       firstValueFound = true;
96     }
97   }
98 
99   // No value found, so return null to content
100   if (!firstValueFound) {
101     aValue.SetIsVoid(true);
102   }
103 }
104 
GetFirst(const nsACString & aName,nsACString & aValue,ErrorResult & aRv) const105 void InternalHeaders::GetFirst(const nsACString& aName, nsACString& aValue,
106                                ErrorResult& aRv) const {
107   nsAutoCString lowerName;
108   ToLowerCase(aName, lowerName);
109 
110   if (IsInvalidName(lowerName, aRv)) {
111     return;
112   }
113 
114   for (uint32_t i = 0; i < mList.Length(); ++i) {
115     if (lowerName == mList[i].mName) {
116       aValue = mList[i].mValue;
117       return;
118     }
119   }
120 
121   // No value found, so return null to content
122   aValue.SetIsVoid(true);
123 }
124 
Has(const nsACString & aName,ErrorResult & aRv) const125 bool InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const {
126   nsAutoCString lowerName;
127   ToLowerCase(aName, lowerName);
128 
129   if (IsInvalidName(lowerName, aRv)) {
130     return false;
131   }
132 
133   for (uint32_t i = 0; i < mList.Length(); ++i) {
134     if (lowerName == mList[i].mName) {
135       return true;
136     }
137   }
138   return false;
139 }
140 
Set(const nsACString & aName,const nsACString & aValue,ErrorResult & aRv)141 void InternalHeaders::Set(const nsACString& aName, const nsACString& aValue,
142                           ErrorResult& aRv) {
143   nsAutoCString lowerName;
144   ToLowerCase(aName, lowerName);
145   nsAutoCString trimValue;
146   NS_TrimHTTPWhitespace(aValue, trimValue);
147 
148   if (IsInvalidMutableHeader(lowerName, trimValue, aRv)) {
149     return;
150   }
151 
152   SetListDirty();
153 
154   int32_t firstIndex = INT32_MAX;
155 
156   // remove in reverse order to minimize copying
157   for (int32_t i = mList.Length() - 1; i >= 0; --i) {
158     if (lowerName == mList[i].mName) {
159       firstIndex = std::min(firstIndex, i);
160       mList.RemoveElementAt(i);
161     }
162   }
163 
164   if (firstIndex < INT32_MAX) {
165     Entry* entry = mList.InsertElementAt(firstIndex);
166     entry->mName = lowerName;
167     entry->mValue = trimValue;
168   } else {
169     mList.AppendElement(Entry(lowerName, trimValue));
170   }
171 }
172 
Clear()173 void InternalHeaders::Clear() {
174   SetListDirty();
175   mList.Clear();
176 }
177 
SetGuard(HeadersGuardEnum aGuard,ErrorResult & aRv)178 void InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) {
179   // The guard is only checked during ::Set() and ::Append() in the spec.  It
180   // does not require revalidating headers already set.
181   mGuard = aGuard;
182 }
183 
~InternalHeaders()184 InternalHeaders::~InternalHeaders() {}
185 
186 // static
IsSimpleHeader(const nsACString & aName,const nsACString & aValue)187 bool InternalHeaders::IsSimpleHeader(const nsACString& aName,
188                                      const nsACString& aValue) {
189   // Note, we must allow a null content-type value here to support
190   // get("content-type"), but the IsInvalidValue() check will prevent null
191   // from being set or appended.
192   return aName.EqualsLiteral("accept") ||
193          aName.EqualsLiteral("accept-language") ||
194          aName.EqualsLiteral("content-language") ||
195          (aName.EqualsLiteral("content-type") &&
196           nsContentUtils::IsAllowedNonCorsContentType(aValue));
197 }
198 
199 // static
IsRevalidationHeader(const nsACString & aName)200 bool InternalHeaders::IsRevalidationHeader(const nsACString& aName) {
201   return aName.EqualsLiteral("if-modified-since") ||
202          aName.EqualsLiteral("if-none-match") ||
203          aName.EqualsLiteral("if-unmodified-since") ||
204          aName.EqualsLiteral("if-match") || aName.EqualsLiteral("if-range");
205 }
206 
207 // static
IsInvalidName(const nsACString & aName,ErrorResult & aRv)208 bool InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv) {
209   if (!NS_IsValidHTTPToken(aName)) {
210     NS_ConvertUTF8toUTF16 label(aName);
211     aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(label);
212     return true;
213   }
214 
215   return false;
216 }
217 
218 // static
IsInvalidValue(const nsACString & aValue,ErrorResult & aRv)219 bool InternalHeaders::IsInvalidValue(const nsACString& aValue,
220                                      ErrorResult& aRv) {
221   if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
222     NS_ConvertUTF8toUTF16 label(aValue);
223     aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(label);
224     return true;
225   }
226   return false;
227 }
228 
IsImmutable(ErrorResult & aRv) const229 bool InternalHeaders::IsImmutable(ErrorResult& aRv) const {
230   if (mGuard == HeadersGuardEnum::Immutable) {
231     aRv.ThrowTypeError<MSG_HEADERS_IMMUTABLE>();
232     return true;
233   }
234   return false;
235 }
236 
IsForbiddenRequestHeader(const nsACString & aName) const237 bool InternalHeaders::IsForbiddenRequestHeader(const nsACString& aName) const {
238   return mGuard == HeadersGuardEnum::Request &&
239          nsContentUtils::IsForbiddenRequestHeader(aName);
240 }
241 
IsForbiddenRequestNoCorsHeader(const nsACString & aName) const242 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
243     const nsACString& aName) const {
244   return mGuard == HeadersGuardEnum::Request_no_cors &&
245          !IsSimpleHeader(aName, EmptyCString());
246 }
247 
IsForbiddenRequestNoCorsHeader(const nsACString & aName,const nsACString & aValue) const248 bool InternalHeaders::IsForbiddenRequestNoCorsHeader(
249     const nsACString& aName, const nsACString& aValue) const {
250   return mGuard == HeadersGuardEnum::Request_no_cors &&
251          !IsSimpleHeader(aName, aValue);
252 }
253 
IsForbiddenResponseHeader(const nsACString & aName) const254 bool InternalHeaders::IsForbiddenResponseHeader(const nsACString& aName) const {
255   return mGuard == HeadersGuardEnum::Response &&
256          nsContentUtils::IsForbiddenResponseHeader(aName);
257 }
258 
Fill(const InternalHeaders & aInit,ErrorResult & aRv)259 void InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv) {
260   const nsTArray<Entry>& list = aInit.mList;
261   for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) {
262     const Entry& entry = list[i];
263     Append(entry.mName, entry.mValue, aRv);
264   }
265 }
266 
Fill(const Sequence<Sequence<nsCString>> & aInit,ErrorResult & aRv)267 void InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit,
268                            ErrorResult& aRv) {
269   for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) {
270     const Sequence<nsCString>& tuple = aInit[i];
271     if (tuple.Length() != 2) {
272       aRv.ThrowTypeError<MSG_INVALID_HEADER_SEQUENCE>();
273       return;
274     }
275     Append(tuple[0], tuple[1], aRv);
276   }
277 }
278 
Fill(const Record<nsCString,nsCString> & aInit,ErrorResult & aRv)279 void InternalHeaders::Fill(const Record<nsCString, nsCString>& aInit,
280                            ErrorResult& aRv) {
281   for (auto& entry : aInit.Entries()) {
282     Append(entry.mKey, entry.mValue, aRv);
283     if (aRv.Failed()) {
284       return;
285     }
286   }
287 }
288 
289 namespace {
290 
291 class FillHeaders final : public nsIHttpHeaderVisitor {
292   RefPtr<InternalHeaders> mInternalHeaders;
293 
294   ~FillHeaders() = default;
295 
296  public:
297   NS_DECL_ISUPPORTS
298 
FillHeaders(InternalHeaders * aInternalHeaders)299   explicit FillHeaders(InternalHeaders* aInternalHeaders)
300       : mInternalHeaders(aInternalHeaders) {
301     MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders);
302   }
303 
304   NS_IMETHOD
VisitHeader(const nsACString & aHeader,const nsACString & aValue)305   VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
306     mInternalHeaders->Append(aHeader, aValue, IgnoreErrors());
307     return NS_OK;
308   }
309 };
310 
311 NS_IMPL_ISUPPORTS(FillHeaders, nsIHttpHeaderVisitor)
312 
313 }  // namespace
314 
FillResponseHeaders(nsIRequest * aRequest)315 void InternalHeaders::FillResponseHeaders(nsIRequest* aRequest) {
316   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
317   if (!httpChannel) {
318     return;
319   }
320 
321   RefPtr<FillHeaders> visitor = new FillHeaders(this);
322   nsresult rv = httpChannel->VisitResponseHeaders(visitor);
323   if (NS_FAILED(rv)) {
324     NS_WARNING("failed to fill headers");
325   }
326 }
327 
HasOnlySimpleHeaders() const328 bool InternalHeaders::HasOnlySimpleHeaders() const {
329   for (uint32_t i = 0; i < mList.Length(); ++i) {
330     if (!IsSimpleHeader(mList[i].mName, mList[i].mValue)) {
331       return false;
332     }
333   }
334 
335   return true;
336 }
337 
HasRevalidationHeaders() const338 bool InternalHeaders::HasRevalidationHeaders() const {
339   for (uint32_t i = 0; i < mList.Length(); ++i) {
340     if (IsRevalidationHeader(mList[i].mName)) {
341       return true;
342     }
343   }
344 
345   return false;
346 }
347 
348 // static
BasicHeaders(InternalHeaders * aHeaders)349 already_AddRefed<InternalHeaders> InternalHeaders::BasicHeaders(
350     InternalHeaders* aHeaders) {
351   RefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders);
352   ErrorResult result;
353   // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
354   // must succeed.
355   basic->Delete(NS_LITERAL_CSTRING("Set-Cookie"), result);
356   MOZ_ASSERT(!result.Failed());
357   basic->Delete(NS_LITERAL_CSTRING("Set-Cookie2"), result);
358   MOZ_ASSERT(!result.Failed());
359   return basic.forget();
360 }
361 
362 // static
CORSHeaders(InternalHeaders * aHeaders)363 already_AddRefed<InternalHeaders> InternalHeaders::CORSHeaders(
364     InternalHeaders* aHeaders) {
365   RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
366   ErrorResult result;
367 
368   nsAutoCString acExposedNames;
369   aHeaders->GetFirst(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
370                      acExposedNames, result);
371   MOZ_ASSERT(!result.Failed());
372 
373   AutoTArray<nsCString, 5> exposeNamesArray;
374   nsCCharSeparatedTokenizer exposeTokens(acExposedNames, ',');
375   while (exposeTokens.hasMoreTokens()) {
376     const nsDependentCSubstring& token = exposeTokens.nextToken();
377     if (token.IsEmpty()) {
378       continue;
379     }
380 
381     if (!NS_IsValidHTTPToken(token)) {
382       NS_WARNING(
383           "Got invalid HTTP token in Access-Control-Expose-Headers. Header "
384           "value is:");
385       NS_WARNING(acExposedNames.get());
386       exposeNamesArray.Clear();
387       break;
388     }
389 
390     exposeNamesArray.AppendElement(token);
391   }
392 
393   nsCaseInsensitiveCStringArrayComparator comp;
394   for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) {
395     const Entry& entry = aHeaders->mList[i];
396     if (entry.mName.EqualsASCII("cache-control") ||
397         entry.mName.EqualsASCII("content-language") ||
398         entry.mName.EqualsASCII("content-type") ||
399         entry.mName.EqualsASCII("expires") ||
400         entry.mName.EqualsASCII("last-modified") ||
401         entry.mName.EqualsASCII("pragma") ||
402         exposeNamesArray.Contains(entry.mName, comp)) {
403       cors->Append(entry.mName, entry.mValue, result);
404       MOZ_ASSERT(!result.Failed());
405     }
406   }
407 
408   return cors.forget();
409 }
410 
GetEntries(nsTArray<InternalHeaders::Entry> & aEntries) const411 void InternalHeaders::GetEntries(
412     nsTArray<InternalHeaders::Entry>& aEntries) const {
413   MOZ_ASSERT(aEntries.IsEmpty());
414   aEntries.AppendElements(mList);
415 }
416 
GetUnsafeHeaders(nsTArray<nsCString> & aNames) const417 void InternalHeaders::GetUnsafeHeaders(nsTArray<nsCString>& aNames) const {
418   MOZ_ASSERT(aNames.IsEmpty());
419   for (uint32_t i = 0; i < mList.Length(); ++i) {
420     const Entry& header = mList[i];
421     if (!InternalHeaders::IsSimpleHeader(header.mName, header.mValue)) {
422       aNames.AppendElement(header.mName);
423     }
424   }
425 }
426 
MaybeSortList()427 void InternalHeaders::MaybeSortList() {
428   class Comparator {
429    public:
430     bool Equals(const Entry& aA, const Entry& aB) const {
431       return aA.mName == aB.mName;
432     }
433 
434     bool LessThan(const Entry& aA, const Entry& aB) const {
435       return aA.mName < aB.mName;
436     }
437   };
438 
439   if (!mListDirty) {
440     return;
441   }
442 
443   mListDirty = false;
444 
445   Comparator comparator;
446 
447   mSortedList.Clear();
448   for (const Entry& entry : mList) {
449     bool found = false;
450     for (Entry& sortedEntry : mSortedList) {
451       if (sortedEntry.mName == entry.mName) {
452         sortedEntry.mValue += ", ";
453         sortedEntry.mValue += entry.mValue;
454         found = true;
455         break;
456       }
457     }
458 
459     if (!found) {
460       mSortedList.InsertElementSorted(entry, comparator);
461     }
462   }
463 }
464 
SetListDirty()465 void InternalHeaders::SetListDirty() {
466   mSortedList.Clear();
467   mListDirty = true;
468 }
469 
470 }  // namespace dom
471 }  // namespace mozilla
472