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